• UVAlive-7040 color(组合数学,二项式反演)


    链接:vjudge

    题目大意:有一排方格共 $n$ 个,现在有 $m$ 种颜色,要给这些方格染色,要求相邻两个格子的颜色不能相同。现在问恰好用了 $k$ 种颜色的合法方案数。答案对 $10^9+7$ 取模。$T$ 组数据。

    $1le Tle 300,1le n,mle 10^9,1le kle 10^6,kle min(n,m)$。大多数数据中 $k$ 很小。(smg啊……)


    经典的二项式反演例题。

    我们令 $f(x)$ 为一共有 $x$ 种颜色,恰好用了 $x$ 种颜色的方案数。

    答案就是 ${mchoose k}f(k)$。因为任意选 $k$ 种颜色方案数是一样的。

    这……似乎不太好算?

    我们再令 $g(x)$ 为一共有 $x$ 种颜色,用了至多 $x$ 种颜色的方案数。

    这个就不难算了。第一个格子可以随便填,就是 $x$ 种。后面的格子只要不和上一个颜色相同就行了,就是 $x-1$ 种。

    乘法原理一下:$g(x)=x(x-1)^{n-1}$。$x=0$ 时这个式子是 $0$。

    但是要注意,$x=n=1$ 时我们这样计算是 $0$,但是实际上是 $1$。为什么?$1 imes 0^0$。所以我们要把 $0^0$ 看做 $1$,或者直接特判掉。

    (但是不特判也能过,数据太水,这多组数据没用吧)

    我们来想一想 $f$ 和 $g$ 有什么关系。很容易发现:$g(x)=sumlimits^x_{i=0}{xchoose i}f(i)$。因为 $x$ 种颜色中随便选 $i$ 种都可以。

    标准二项式反演形式。$f(x)=sumlimits^x_{i=0}(-1)^{x-i}{xchoose i}g(i)$。

     因为 $xle 10^6$,所以阶乘和逆元都可以预处理,组合数就可以 $O(1)$ 了。此时 $f(x)$ 就可以 $O(xlog n)$ 算了。

    现在问题就是算 $mchoose k$ 了。$m$ 达到了惊人的 $10^9$,模数又是个大数……怎么办?

    我们发现 $mchoose k$ 可以表示成一种不常用的形式:$frac{m(m-1)(m-2)...(m-k+1)}{k!}$。

    此时分母是预处理过的,分子可以 $O(k)$ 算。这就完事了。

    总时间复杂度 $O(Tklog n)$。因为大多数数据中 $k$ 很小,所以可以跑过。

    ……这数据范围我给满分……

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=1000100,mod=1000000007;
    #define FOR(i,a,b) for(int i=(a);i<=(b);i++)
    #define ROF(i,a,b) for(int i=(a);i>=(b);i--)
    #define MEM(x,v) memset(x,v,sizeof(x))
    inline int read(){
        char ch=getchar();int x=0,f=0;
        while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
        while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
        return f?-x:x;
    }
    int t,n,m,k,fac[maxn],inv[maxn],invfac[maxn];
    void init(){    //预处理阶乘,逆元,阶乘的逆元
        fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
        FOR(i,2,1000000){
            fac[i]=1ll*fac[i-1]*i%mod;
            inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
            invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
        }
    }
    int C(int n,int m){
        if(n<=1000000) return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;    //n,m很小,可以直接算
        int ans=invfac[m];    //分母是m的阶乘
        ROF(i,n,n-m+1) ans=1ll*ans*i%mod;    //暴力乘上分子
        return ans;
    }
    int qpow(int a,int b){    //快速幂
        int ans=1;
        for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
        return ans;
    }
    int g(int x){
        if(x==1 && n==1) return 1;    //特判掉x=n=1
        return 1ll*x*qpow(x-1,n-1)%mod;
    }
    int f(int x){
        int ans=0;
        FOR(i,0,x){
            int v=1ll*C(x,i)*g(i)%mod;
            if((x-i)&1) ans=(ans-v+mod)%mod;    //(-1)^(x-i)
            else ans=(ans+v)%mod;
        }
        return ans;
    }
    int main(){
        init();
        t=read();
        FOR(tt,1,t){
            n=read();m=read();k=read();
            printf("Case #%d: %d
    ",tt,int(1ll*C(m,k)*f(k)%mod));    //记得乘上C(m,k)
        }
    }
    二项式反演
  • 相关阅读:
    P1119 灾后重建
    P1824 进击的奶牛
    P3743 kotori的设备
    【MM配置】SAP MM模块配置目录(转)
    【SAP参数文件】SAP参数文件(转)
    【MM】供应商删除
    【Debug】修改数据库表数据的方法
    【MM 单位换算】物料基本单位换算
    EDI RFC IDOC
    【打印配置】SAP打印机配置
  • 原文地址:https://www.cnblogs.com/1000Suns/p/10353695.html
Copyright © 2020-2023  润新知