【GDOI2017模拟9.24】周末晚会

来源:互联网 发布:幼儿淘宝海报怎么做 编辑:程序博客网 时间:2024/05/21 14:40

Description

求n个点的圆排列,每个点是1或0,并且连续的0不超过k个的方案数。
循环同构算一种。多组数据。
T<=50,n,k<=2000

Solution

首先,先不考虑环的情况,我们来处理一下连续的k个。
一个很显然的想法是,Fi,j表示,前i个数,第1个是1且后面j个是0的方案数。
看一下这一位放0还是1就可以推出转移方程了。
如果是在圆上呢?
那么我们设ri表示长度为i的圆的方案数。
因为我们保证了第一个是1,所以枚举圆方案的两边加起来为j,那么就有j+1种方法。
于是

r[i]=j=0kF[i][j](j+1)

然后,我们考虑循环同构。
设gi表示长度为i的没有循环节的方法。
然后很显然可以用容斥解决。
枚举i的循环节长度,然后把对应的方案减去。
g[i]=r[i]j|i,j<ig[j]

所以答案就是
i|ng[i]i

因为考虑循环节就是i,所以除掉。
还有,如果k>=n,答案要+1(毕竟无法处理全是0的情况)

Code

#include<cstdio>#include<cstring>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define N 2005using namespace std;typedef long long ll;const int mo=1e8+7;ll f[N][N],r[N],g[N],ans;int n,k,ty;ll mi(ll x,int y) {    ll z=1;    for(;y;y/=2,x=x*x%mo) if (y&1) z=z*x%mo;    return z;}int main() {    for(scanf("%d",&ty);ty;ty--) {        scanf("%d%d",&n,&k);ans=0;        if (k>=n) ans=1;        if (ty==34) {            ans=ans;            k=k;        }        // 1        fo(i,1,n) f[i][0]=0;f[1][0]=1;        fo(i,1,n-1) fo(j,0,k) f[i+1][j+1]=f[i][j],f[i+1][0]=(f[i+1][0]+f[i][j])%mo;        // 2        fo(i,1,n) {            r[i]=0;            fo(j,0,k) r[i]=(r[i]+(ll)f[i][j]*(j+1)%mo)%mo;            g[i]=r[i];        }        fo(i,1,n) fo(j,1,i-1) if (!(i%j)) g[i]=(g[i]-g[j]+mo)%mo;        fo(i,1,n) if (!(n%i)) ans=(ans+g[i]*mi(i,mo-2)%mo)%mo;        printf("%lld\n",ans);     }}
0 0
原创粉丝点击