UVA 11806 Cheerleaders (组合+容斥原理)

自己写的代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
/*
题意:相当于在一个m*n的矩形网格里放k个相同的石子,问有多少种方法?
      限制条件:每个格子最多放一个石子,所有石子都要用完,并且第一行、最后一行、第一列、最后一列都得有石子。
思路:
    直接求的话会比较麻烦,反过来想:
    设总方案数为S,A={第一行没有石子},B={最后一行没有石子},C={第一列没有石子},D={最后一列没有石子}
    利用容斥原理,先求|A并B并C并D|,然后再用|s|-|A并B并C并D|,即为答案。
    而对于有r行,t列,摆放k个石子的方案数为C(r*t,k)。
*/
using namespace std;
const int maxn=25;
const int mod=1000007;
long long c[maxn*maxn][maxn*maxn];
int t,m,n,k;
void init(){
    memset(c,0,sizeof(c)); //先初始化为0,因为在计算容斥原理的时候,很有可能会出现C(i,j)(j>i)的情形,此时应该值为0
    c[0][0]=1;
    for(int i=1;i<maxn*maxn;i++){     //求出组合数
        c[i][0]=1;
        for(int j=1;j<i;j++)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
        c[i][i]=1;
    }
}
int main()
{
    long long ans,tmp;
    init();
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        ans=0;
        scanf("%d%d%d",&m,&n,&k);
        if(k>m*n||k<2)
            ans=0;
        else{
            //先求|A并B并C并D|,由于只有四个元素,所以直接写出式子了
            ans=(2*c[(m-1)*n][k]+2*c[m*(n-1)][k])%mod;
            tmp=((c[(m-2)*n][k]+4*c[(m-1)*(n-1)][k]%mod)%mod+c[(n-2)*m][k])%mod;
            ans=(ans-tmp+mod)%mod;
            ans=(ans+2*c[(m-2)*(n-1)][k])%mod;
            ans=(ans+2*c[(m-1)*(n-2)][k])%mod;
            ans=(ans+mod-c[(m-2)*(n-2)][k])%mod;

            ans=(c[m*n][k]-ans+mod)%mod;  //最后再用所有总的方案数减去ans值,即为最后要求的答案
        }
        printf("Case %d: %lld
",i,ans);
    }
    return 0;
}

白书上的代码:

#include <iostream>
#include <stdio.h>
#include <string.h>
/*
题意:相当于在一个m*n的矩形网格里放k个相同的石子,问有多少种方法?
      限制条件:每个格子最多放一个石子,所有石子都要用完,并且第一行、最后一行、第一列、最后一列都得有石子。
思路:
    直接求的话会比较麻烦,反过来想:
    设总方案数为S,A={第一行没有石子},B={最后一行没有石子},C={第一列没有石子},D={最后一列没有石子}
    利用容斥原理,先求|A并B并C并D|,然后再用|s|-|A并B并C并D|,即为答案。
    而对于有r行,t列,摆放k个石子的方案数为C(r*t,k)。
*/
using namespace std;
const int maxn=25;
const int mod=1000007;
int C[maxn*maxn][maxn*maxn];
int t,m,n,k;
void init(){
    memset(C,0,sizeof(C)); //先初始化为0,因为在计算容斥原理的时候,很有可能会出现C(i,j)(j>i)的情形,此时应该值为0
    C[0][0]=1;
    for(int i=1;i<maxn*maxn;i++){     //求出组合数
        C[i][0]=1;
        for(int j=1;j<i;j++)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        C[i][i]=1;
    }
}

int main()
{
    init();
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        int sum=0;
        scanf("%d%d%d",&m,&n,&k);
        //枚举所有16种搭配方式,s=0表明是总的方案数
        //由于最后我们求的是补给的个数,所以在用容斥原理的时候稍作修改:
        //原本奇数个集合是加,改为减;偶数个集合是减,改为加
        for(int s=0;s<16;s++){
            int b=0,r=n,c=m; //b统计该方案数对应的集合的个数,r和c是可以放置的行列数
            if(s&1){
                b++;
                r--;
            }
            if(s&(1<<1)){
                b++;
                r--;
            }
            if(s&(1<<2)){
                b++;
                c--;
            }
            if(s&(1<<3)){
                b++;
                c--;
            }
            if(b&1)
                sum=(sum+mod-C[r*c][k])%mod;  //奇数个集合,做减法
            else
                sum=(sum+C[r*c][k])%mod;  //偶数个集合,做加法
        }
        printf("Case %d: %d
",i,sum);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/chenxiwenruo/p/3399186.html