[JZOJ5728]简单计数||
Posted ZLTJohn
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JZOJ5728]简单计数||相关的知识,希望对你有一定的参考价值。
题目描述
解题思路
乍一看不是很会。
先考虑不是环怎么做。
考虑分类地计数,即把方案归到某一类型里,再分别计算每一个类型的数量来求答案。
最终一种方案肯定有若干段相同颜色段,我们可以直接考虑每一种颜色的划分贡献,然后再算出他们组合起来的方案数。
具体地,我们计算出f(i,j)表示把i个相同的球分成j段的贡献和,一种方案贡献为每段大小乘积。然后,我们把同颜色的i个球缩成j个,然后问题就变成要计算有多少种不同的排列方案,使得相同颜色的球不相邻,设为F
设a[i]表示颜色i分成的段数,那么答案就是
∑a[1..n]∏if(c[i],a[i])∗F(a[1..n])
∑
a
[
1..
n
]
∏
i
f
(
c
[
i
]
,
a
[
i
]
)
∗
F
(
a
[
1..
n
]
)
,其中F就是排列方案。
考虑F怎么求,我们可以做容斥。对于一种a[1..n],考虑枚举一些相邻的同颜色球再缩起来,设剩下每种球个数b[1..n],这些球可以乱放,那么此时容斥贡献是
(∑b[i])!∗∏Cb[i]−1a[i]−1∗(−1)a[i]−b[i]∗1b[i]!
(
∑
b
[
i
]
)
!
∗
∏
C
a
[
i
]
−
1
b
[
i
]
−
1
∗
(
−
1
)
a
[
i
]
−
b
[
i
]
∗
1
b
[
i
]
!
。这个容斥我们可以DP地计算,每次枚举a[i]和b[i]即可,我们还可以在中途把
f(c[i],a[i])
f
(
c
[
i
]
,
a
[
i
]
)
也算进去,这样是没有问题的。设F[i][j]表示做到颜色i,
∑k=1..ib[k]=j
∑
k
=
1..
i
b
[
k
]
=
j
的贡献和,最后再乘
j!
j
!
。
这样不是环的做法就出来了,转移的时候优化一下。
现在考虑环怎么办。我们可以用最小表示法,固定颜色1在开头,且1不能结尾。算出所有方案再乘
∑c[i]
∑
c
[
i
]
。固定开头我们只需要让最后的
j!
j
!
变成
(j−1)!
(
j
−
1
)
!
,而枚举b[1]的时候乘
1(b[1]−1)!
1
(
b
[
1
]
−
1
)
!
。固定结尾类似。不过这样有个问题,如果颜色1分成k段,dp完乘
∑c
∑
c
的时候,这种方案会被算k次,我们在开始的时候除即可。
然后就做完了。
实现的时候没有减掉开头和结尾都是1的方案,不知道为什么减掉错了,估计是已经容斥掉了吧…
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<bitset>
//开 O2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef double db;
const int N=5000+5,mo=1e9+7;
int fac[N],rev[N],f[105][105],F[55][N],sum[N],c[N],A,B,i,j,k,ans,re[N],n,trs[N];
int ksm(int x,int y)
if (x==-1) return (y%2)?-1:1;
int ret=1;
while (y)
if (y&1) ret=1ll*ret*x%mo;
y>>=1;
x=1ll*x*x%mo;
return ret;
void predo(int n)
fac[0]=1;
fo(i,1,n) fac[i]=1ll*fac[i-1]*i%mo;
rev[n]=ksm(fac[n],mo-2);
fd(i,n,1) rev[i-1]=1ll*rev[i]*i%mo;
re[0]=1;
fo(i,1,n) re[i]=1ll*rev[i]*fac[i-1]%mo;
int C(int m,int n)
return 1ll*fac[m]*rev[n]%mo*rev[m-n]%mo;
int main()
freopen("number.in","r",stdin);
//freopen("number.out","w",stdout);
scanf("%d",&n);
fo(i,1,n)
scanf("%d",c+i);
sum[i]=sum[i-1]+c[i];
predo(5e3);
f[0][0]=1;
fo(i,1,100)
fo(j,1,i)
fo(k,1,i)
f[i][j]=(f[i][j]+1ll*f[i-k][j-1]*k)%mo;
F[0][0]=1;
fo(i,0,n)
fo(B,1,c[i+1])
trs[B]=0;
fo(A,B,c[i+1])
trs[B]=(trs[B]+1ll*f[c[i+1]][A]*C(A-1,B-1)%mo*ksm(-1,A-B)*rev[B])%mo;
fo(j,0,sum[i])
fo(B,1,c[i+1])
F[i+1][j+B]=(F[i+1][j+B]+1ll*F[i][j]*trs[B])%mo;
fo(j,0,sum[n])
ans=(ans+1ll*F[n][j]*fac[j-1])%mo;
printf("%d\\n",1ll*ans*sum[n]%mo);
以上是关于[JZOJ5728]简单计数||的主要内容,如果未能解决你的问题,请参考以下文章