JZOJ4800. 【GDOI2017模拟9.24】周末晚会

题目大意

N个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过K个女孩座位是连续的。方案数对1e8+7取模
循环同构会被认为是同一种方案。T组数据
对于20%的数据T<=20,N,K<=20;
对于100%的数据T<=50,N,K<=2000;

分析

我们先不考虑循环同构。
一个圈好麻烦。我们可以先把第一个设为男生,因为整个圈除了没有男生的情况,都会有男生,那我们可以先把圈旋转过来。全部是女生的情况特判就好。
设F[i][j]为共有i个人,最后有连续j个女生,边界:F[1][0]=1。
转移明显:F[i][j]⇒F[i+1][j+1]与F[i+1][0]。
这样会漏掉第一个不是男生的方案,那么对于每一个F[i][j],都乘上(j+1)即可。很简单吧?但是还有循环同构····
怎么办呢?考虑怎样的序列会循环同构。我们可以发现,只有有循环节,设长度x,x|n的时候,才会算重。这样的话,我们可以枚举n的约数来处理。
设r[i]为大小为i的圈的方案数,g[i]为没有循环节的大小为i的圈。
g[i]=r[i]j|i,j<ig[j]
然后 ans=i|ng[i]/i ,记得求i的逆元。

代码

瞎打循环,慢的要死又没用···

#include
#include
#include
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=2005,mo=100000007;
int f[N][N],T,i,j,n,m,k,inv[N],r[N],g[N];
ll ans;
ll ksm(ll x,ll y)
{
    if (y==0) return 1;
    if (y==1) return x;
    ll dur=ksm(x,y/2);
    return dur*dur%mo*ksm(x,y%2)%mo;
}
void predo()
{
    fo(i,1,n)
        fo(j,0,n) f[i][j]=0;
    f[1][0]=1;
    fo(i,1,n-1)
        fo(j,0,min(i-1,m))
        {
            (f[i+1][j+1]+=f[i][j])%=mo;
            (f[i+1][0]+=f[i][j])%=mo;
        }
}
int main()
{
    freopen("t2.in","r",stdin);
    freopen("t2.out","w",stdout);
    scanf("%d\n",&T);
    fo(i,1,2000) inv[i]=ksm(i,mo-2);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        ans=0;
        predo();
        fo(j,1,n)
        {
            r[j]=0;
            fo(i,0,m)
                r[j]=(r[j]+(ll)f[j][i]*i%mo+f[j][i])%mo;
        }
        fo(i,1,n) 
        {
            g[i]=r[i];
            fo(j,1,i-1)
                if (i%j==0)
                    g[i]=(g[i]-g[j]+mo)%mo;
        }
        fo(i,1,n)
            if (n%i==0)
                ans=(ans+(ll)g[i]*inv[i]%mo)%mo;
        if (n<=m) ans=(ans+1)%mo;
        printf("%lld\n",ans);
    }
}

你可能感兴趣的:(思考与总结)