【GDOI2017模拟9.24】周末晚会

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
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 2005
using 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); 
    }
}

你可能感兴趣的:(数论,其他dp)