一個不大不小的日子——我終於註冊了
UVA
這麼個舉世文明的網站,並且答對了幾道題。(但是爲什麼UVA
的註冊信息會跑到
在一個 的矩形網格里放 個相同的石子,問有多少種合格的擺法。一共 組輸入數據。
一個合格的擺法應該滿足一下條件:
- 每個格子裏最多有 個石子。
- 所有的石子都要用完。
- 網格第一行、第一列、最後一行、最後一列都要有石子。
。
翻譯改自 《算法競賽入門經典》
一書。
我們發現直接求有點難,怎麼辦呢?正難則反,我們考慮容斥。
記滿足“第一行沒有石子”的方案集爲 ,“第一列沒有石子”的方案集爲 ,“最後一行沒有石子”的方案集爲 ,“最後一列沒有石子”的方案集爲 ,全集爲 ,則總的答案即爲“在 但不在 中任何一個集合中的元素”的個數。
把第一行、最後一行、第一列、最後一列有無石子的狀態存入一個二進制中( 表示沒有, 表示有),總的狀態可以通過枚舉得到。
考慮對於一個狀態如何求其方案數。我們發現,如果第一行或者最後一行沒有石子的話,我們可以把那一行刪除,即只剩下 個有用行(或者叫備選行)。對於列同理,注意當第一行和最後一行同時沒有石子時,就只剩下 個有用行了。
假設有 個有用行, 個有用列,則總的方案數相當於是在 這麼多個格子選 個格子的方案數,即:
const int mod=1e6+7;//模數
int test_number,C[510][510];
int main(){
scanf("%d",&test_number);
for(int i=0;i<=500;i++){
C[i][i]=C[i][0]=1;
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
for(int T=1;T<=test_number;T++){
register int n,m,k,sum=0;
scanf("%d%d%d",&n,&m,&k);
for(int S=0,r,c,b;S<16;S++){
r=n;c=m;b=0;//初始化變量
if (S&1){r--;b++;}//第1行
if (S&2){r--;b++;}//第n行
if (S&4){c--;b++;}//第1列
if (S&8){c--;b++;}//第m列
if (b&1) sum=(sum-C[r*c][k]+mod)%mod;
else sum=(sum+C[r*c][k])%mod;
}
printf("Case %d: %d\n",T,sum);
}
return 0;
}
容斥和正難則反的思維在實際中的應用是非常廣的,大家一定要注意,平時遇到一道直接計算很棘手的題目時,就可以想象可不可以用這兩種方法。