1014 - 結論題&dp? - Mr. Young's Picture Permutations(POJ 2279)
阿新 • • 發佈:2018-11-10
題意
給出n行,每行有人數限制num[i],並且num[i]>=num[i+1],總人數暫且稱為tot=∑num[i],把1~tot這些數字填入矩陣,使得矩陣滿足每行單調遞增,每列單調遞增,求滿足要求的矩陣數目
分析
一開始是打著做dp的名號,發現了這道題
在《演算法競賽》這本書上,是這樣講的
線性dp
但實際上,由於這道題空間限制比較嚴,我們dp的話會爆記憶體(上一篇部落格有解釋)
所以就涼了……
網上盛傳的做法是利用楊氏矩陣和鉤子公式,我就去學習了一下
本來以為幾行程式碼就搞定,是一道水題,後來發現還是要思考一下的
先來講一下楊氏矩陣吧
楊氏矩陣定義(需滿足的條件/特徵):
(1)若格子(i,j)沒有元素,則該格子的右邊和上邊一定沒有元素;
(2)若格子(i,j)有元素data[i][j],則該格子右邊和上邊相鄰的格子要麼沒有元素,要麼有比data[i][j]大的元素。
顯然有同一些元素組成的楊氏矩陣不唯一,1~n組成楊氏矩陣的個數可以寫出:
F[1]=1,F[2]=2, F[n]=F[n-1]+(n-1)*F[n-2] (n>2)。
(這個東西不會證……但我們很容易發現這簡直就是這道題的模樣啊)
再來說一下鉤子公式(wcr大佬表示非常喜歡這個名字??)
對於給定形狀,不同的楊氏矩陣的個數為(n!/(每個格子的鉤子長度加1的積))。
鉤子長度:該格子右邊的格子數和它上邊的格子數之和;
然後就會發現這道題簡直就是為這個公式的應用而生的
只是……
如果我們簡單套公式的話就會出岔子
因為結合這道題,從前往後值是遞增的,下面的數是大於當前數的,所以我們換一下,把下面看做上面(相當於把矩陣180度旋轉一下)
還有一點由於和階乘有關,我們很容易就炸掉了,所以要一邊處理一邊約分
程式碼
#include<cstdio> #include<iostream> #include<cstring> #define ll long long using namespace std; int k,num[10]; ll sum[200]; ll gcd(ll x,ll y){ ll z=x%y; while(z){x=y;y=z;z=x%y;} return y; } int main(){ while(1){ memset(sum,0,sizeof(sum)); scanf("%d",&k); if(k==0) break; int i,j,n=0; for(i=1;i<=k;++i) scanf("%d",&num[i]); for(i=1;i<=k;++i){ for(j=1;j<=num[i];++j){ ++n; for(int p=i+1;p<=k;++p){ if(num[p]>=j) sum[n]++; else break; } sum[n]+=num[i]-j+1; } } ll x=1,y=1; for(i=1;i<=n;++i){ x*=i;y*=sum[i]; ll tmp=gcd(x,y); x/=tmp;y/=tmp; } printf("%I64d\n",x/y); } return 0; }