1. 程式人生 > >1014 - 結論題&dp? - Mr. Young's Picture Permutations(POJ 2279)

1014 - 結論題&dp? - Mr. Young's Picture Permutations(POJ 2279)

傳送門

 

題意

給出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;
}