zoukankan      html  css  js  c++  java
  • 【XSY3156】简单计数II 容斥 DP

    题目大意

      定义一个序列的权值为:把所有相邻的相同的数合并为一个集合后,所有集合的大小的乘积。

      特别的,第一个数和最后一个数是相邻的。

      现在你有 (n) 种数,第 (i) 种有 (c_i) 个。求所有不同的序列的权值的和。

      (nleq 50,c_ileq 100)

    题解

      考虑第一个数和最后一个数不相邻时怎么做。

      记 (g_{i,j}) 为出现了 (i) 次的数分成 (j) 个集合,所有集合大小的乘积的和。

    [g_{i,j}=sum_{k=1}^ig_{i-k,j-1} imes k ]

      假设最后 (i) 分成了 (a_i) 个集合,那么答案就是 (prod_{i=1}^ng_{c_i,a_i}) 再乘上方案数。

      方案数可以容斥求。

      具体来说,把最后相邻且同色的球合并成一个大球。设最后有 (b_i) 个大球,那么容斥系数就是 ({(-1)}^{a_i-b_i}),带容斥系数的方案数就是 (inom{a_i-1}{b_i-1}{(-1)}^{a_i-b_i})

      最后这 (sum b_i) 个球可以随意放,方案数是 (frac{(sum b_i)!}{prod b_i!})

      总的答案是

    [left(prod_{i=1}^ng_{c_i,a_i}inom{a_i-1}{b_i-1}{(-1)}^{a_i-b_i} ight)frac{(sum_{i=1}^nb_i)!}{prod_{i=1}^n b_i!} ]

      这样就可以 DP 了。(状态为 (i)(sum b_i)

      考虑第一个数和最后一个数相邻时怎么做。

      可以用最小表示法,令第一个数为 (1) 且 最后一个数不为 (1)(除非 (n=1))。

      只需要在后面计算组合数的时候把 (b_1-1) 再除以 (a_1) 就可以得到第一个数为 (1) 的方案数。

      把 (b_1-2) 再除以 (a_1) 就可以得到第一个数为 (1) 且最后一个数也是 (1) 的方案数。

      除以 (a_1) 是因为一个方案会被算多次。

      再把方案数乘以 (sum c_i) 就是答案了。

      时间复杂度:(O((sum c_i)^2))

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const ll p=1000000007;
    ll fac[5010],ifac[5010],inv[5010];
    ll f[60][5010];
    ll g[110][110];
    int a[60];
    int n;
    int s[60];
    ll c[110][110];
    ll c1[110],c2[110];
    ll binom(int x,int y)
    {
    	return x>=y&&y>=0?fac[x]*ifac[y]%p*ifac[x-y]%p:0;
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
    	freopen("c.in","r",stdin);
    	freopen("c.out","w",stdout);
    #endif
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		s[i]=s[i-1]+a[i];
    	}
    	inv[1]=fac[0]=fac[1]=ifac[0]=ifac[1]=1;
    	for(int i=2;i<=5000;i++)
    	{
    		inv[i]=-p/i*inv[p%i]%p;
    		fac[i]=fac[i-1]*i%p;
    		ifac[i]=ifac[i-1]*inv[i]%p;
    	}
    	g[0][0]=1;
    	for(int i=1;i<=100;i++)
    		for(int j=1;j<=100;j++)
    			for(int k=1;k<=i;k++)
    				g[i][j]=(g[i][j]+g[i-k][j-1]*k)%p;
    	f[0][0]=1;
    	for(int i=1;i<n;i++)
    		for(int j=1;j<=a[i];j++)
    			for(int k=1;k<=j;k++)
    				c[i][k]=(c[i][k]+g[a[i]][j]*binom(j-1,k-1)%p*((j-k)&1?-1:1))%p;
    	for(int i=n;i<=n;i++)
    		for(int j=1;j<=a[i];j++)
    			for(int k=1;k<=j;k++)
    				c1[k]=(c1[k]+g[a[i]][j]*binom(j-1,k-1)%p*((j-k)&1?-1:1)*inv[j])%p;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=a[i];j++)
    			for(int k=0;k<=s[i-1];k++)
    				f[i][k+j]=(f[i][k+j]+f[i-1][k]*c[i][j]%p*binom(k+j,k))%p;
    	ll ans=0;
    	for(int j=1;j<=a[n];j++)
    		for(int k=0;k<=s[n-1];k++)
    			ans=(ans+f[n-1][k]*c1[j]%p*(binom(k+j-1,k)-binom(k+j-2,k)))%p;
    	ans=ans*s[n]%p;
    	ans=(ans+p)%p;
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    
  • 相关阅读:
    Java8新特性一览表
    FastDFS 单机部署指南
    EntityManager的Clear方法的使用
    元类
    python中的函数的分类
    python中的多任务
    正则表达式
    GIL和copy
    文件管理
    logging日志模块配置
  • 原文地址:https://www.cnblogs.com/ywwyww/p/9279670.html
Copyright © 2011-2022 走看看