UVALive 7040 Color (容斥原理 + 组合数学递推公式 + 求逆元 + 基础数论)

搬运题解系列:http://blog.csdn.net/qingshui23/article/details/51125323

传送门 
英文题目:

Recently, Mr. Big recieved n owers from his fans. He wants to recolor those owers with m colors. The 
owers are put in a line. It is not allowed to color any adjacent owers with the same color. Flowers i and 
i + 1 are said to be adjacent for every i, 1 ≤ i < n. Mr. Big also wants the total number of different 
colors of the n owers being exactly k. 
Two ways are considered different if and only if there is at least one ower being colored with different 
colors.

插图:

题目描述的插图

题目大意: 
首先有T组数据,每组数据有 3 个数 n, m, k,分别代表一共有 n 个方格,m种颜色,而我们要 恰好(注意是恰好) 使用 k 中颜色对这些方格进行涂色,并且保证的是每两个相邻的方格的颜色必须是不一样的。

解题思路: 
首先拿过这个题来 不要急着就是说一眼就做出来,所以我们先对它分析一下,我们要从 m 种颜色中选出 k 个进行涂色 所以用的方法数就是 C(m,k),然后对这 n 个方格涂色,第一个 有 k 种选择, 而后边的 n-1 个方格中只有 k-1 种选择,所以 就有公式

S=C(m,k)k(k1)n1

然后这并不是最后的结果,这是 所选的颜色不超过 k 种的方法数,而不是 恰好 用 k 种颜色的,然后就可以用容斥原理来求了,假设集合 Ai 表示 i 号颜色不被选,所以 我们要求的结果
ans=S(A1A2...An)

A1 ⋃ A2 ⋃… ⋃ An 就得通过容斥原理来做啦。。。 
根据上面我们可以求出从 k 种颜色种选 k-i个,剩下的就是 n-1个方格中有 k-i-1中选择。所以这个公式就是: 
C(k,ki)(ki)(ki1)n1

然后考虑的就是奇数加 偶数减就行了。然后再注意一下细节问题: 
1.怎么样求C(m,k),这个就是用到 递推公式了首先给出一个公式 
C(x,i)=C(x,i1)ni+1i

这个公式很好推导: 
C(n,i)=n!i!(ni)!=n!i(i1)!(ni+1)!/(ni+1)=n!(i1)!(ni+1)!ni+1i=C(x,i1)ni+1i

所以根据这个公式我们可以打个表就能算 C(m,k)和 C(k,i)了 
2.求逆元的问题。因为 C(x,i)=C(x,i-1)*(x-i+1)/i可以通过扩展欧几里得算法来算,但是要记得的是要用一个数组保存一下 每一个的逆元,要不然每次调用函数的话 会超时。 
3.就是容斥的详细过程了。记住一点 奇数加偶数减就行 


#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define MOD 1000000007
#define N 1000005
LL n, m, k;
LL inv[N], cm[N], ck[N];
void exgcd(const LL a, const LL b, LL &x, LL &y)
{
    if (!b) x = 1, y = 0;
    else exgcd(b, a % b, y, x), y -= x * (a / b);
}
void Get_Inv()
{
    inv[1]=1;
    for(int i=2; i<N; i++)
    {
        LL y;
        exgcd(i, MOD, inv[i], y);
        inv[i]=(inv[i]%MOD+MOD)%MOD;
    }
}
void Get_Fac()
{
    cm[0]=ck[0]=1;
    for(int i=1; i<=k; i++)
    {
        cm[i]=(cm[i-1]%MOD*(m-i+1)%MOD*inv[i]%MOD)%MOD;
        ck[i]=(ck[i-1]%MOD*(k-i+1)%MOD*inv[i]%MOD)%MOD;
    }
}
LL quick_pow(const LL p, const LL q)
{
    LL ans=1;
    for (LL num=p, t=q; t; num= num*num%MOD, t>>=1) if(t&1) ans=ans*num%MOD;
    return ans;
}
int main()
{
    int t;
    Get_Inv();
    scanf("%d", &t);
    for(int kase=1; kase<=t; kase++)
    {
        scanf("%I64d%I64d%I64d", &n, &m, &k);
        Get_Fac();
        LL ans=0, ret=1;
        for(int i=k; i>=1; i--)
        {
            ans=(ans+ret*i*ck[i]%MOD*quick_pow(i-1, n-1)%MOD+MOD)%MOD;
            ret=-ret;
        }
        ans=ans*cm[k]%MOD;
        printf("Case #%d: %lld\n", kase, ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值