uvalive7040 / cf gym 100548 Color(2014西安区域赛F题)

Source

CF GYM 100548
UVALIVE 7040

题意

n个格子排成一行,有m种颜色,问用恰好k种颜色进行染色,使得相邻格子颜色不同的方案数。
k106n,m109

分析

在网上看到的几篇解题报告好像没讲清楚为什么是容斥(反正我没看懂。。),所以我也写一篇。
首先,我们可以从m个颜色中取出k个,即 Ckm
接着容易想到 k(k1)n1 , 这个是使用不超过k种颜色的所有方案。但我们要求的是恰好使用k种颜色。
假设选出的k种颜色标号为1,2,3,…, k,那么记 Ai 不使用颜色i的方案数,求的就是 |S||A1A2An| 。也就是反过来考虑,我们不考虑用了哪些颜色,我们考虑哪些颜色没用!减去所有有没使用颜色的方案的并集,剩下的方案就是使用了所有k种颜色的方案。上式中的 |S| k(k1)n1 ,后者就可以用容斥原理来求了。注意到我们只是给颜色标了个号,所以后面每一项的应为 Cik(ki)(ki1)n1 的形式,即选出i个不使用的颜色,用剩余颜色去涂的方案数。完整式子写起来比较麻烦就不写了,可以参考其他blog。

代码

/*************************************************************************
    > File Name: uvalive_7040.cpp
    > Author: james47
    > Mail: 
    > Created Time: Sat Aug  8 08:47:40 2015
 ************************************************************************/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int mod = (int)1e9+7;
int pow_mod(int a, int exp){
    int ret = 1;
    while(exp){
        if (exp&1) ret = (long long)ret * a % mod;
        a = (long long)a * a % mod;
        exp >>= 1;
    }
    return ret;
}
int c[(int)1e6+100];
int inv[(int)1e6+100];
int cal(int n, int m, int k){
    if (k == 1 && n == 1) return m;
    if (k == 1 && n > 1) return 0;
    int ret = 1, lim = min(k, m - k);
    for (int i = 1; i <= lim; i++) ret = (long long)ret * (m-i+1) % mod * inv[i] % mod;
    c[0] = 1;
    for (int i = 1; i <= k; i++) c[i] = (long long)c[i-1] * (k-i+1) % mod * inv[i] % mod;
    int tmp, tot = (long long)k * pow_mod(k-1, n-1) % mod;
    for (int i = 1; i+1 < k; i++){
        tmp = (long long)c[i] * (k-i) % mod * pow_mod(k-i-1, n-1) % mod;
        if (i&1){
            tot = tot - tmp;
            if (tot < 0) tot += mod;
        }
        else{
            tot = tot + tmp;
            if (tot >= mod) tot -= mod;
        }
    }
    ret = (long long)ret * tot % mod;
    return ret;
}
void init(){
    inv[1] = 1;
    for (int i = 2; i <= 1000000; i++){
        inv[i] = (long long)(mod - mod/i) * inv[mod % i] % mod;
    }
}
int T, n, m, k;
int main()
{
    init();
    scanf("%d", &T); int cas = 0;
    while(T--){
        scanf("%d %d %d", &n, &m, &k);
        printf("Case #%d: %d\n", ++cas, cal(n, m, k));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值