周末晚会

题目描述

Irena和Sirup正准备下个周末的Party。为这个Party,他们刚刚买了一个非常大的圆桌。他们想邀请每个人,但他们现在不知道如何分配座次。Irena说当有超过K个女孩座位相邻(即这些女孩的座位是连续的,中间没有男孩)的话,她们就会说一整晚的话而不和其他人聊天。
Sirup没有其他选择,只有同意她。然而,作为一名数学家,他很快地痴迷于所有可能方案。
题目说明:
N个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过K个女孩座位是连续的。
循环同构会被认为是同一种方案。

Burnside定理

我们回忆Burnside定理:
假设第i种置换下合法不动点数量为g(i)
在n中置换的约束下,不同方案数为 ni=1g(i)n
因此我们想要计算g。
这题也有n种置换,第i种就是左移i格
我们考虑如何求g[i],首先因为左移i格不动,所以a[0]=a[i]=a[i*2]=……
什么时候会转回来呢?也就是从0开始走什么时候会回到0?
ix0(modn)
x0(modn(n,i))
所以 x=n(n,i)
于是我们知道一共有(n,i)个这样的环,出发点是0,1,2……(n,i)-1
我们现在思考,一个合法的第i种置换下不动点,不能有连续的超过k个1,首尾相接也不能有,那是什么情况呢?
首先,我们先排除k=n的情况,因为此时 g[i]=2(n,i)
然后,如果(n,i)<=k,那么只有全是1的情况才非法,因此 g[i]=2(n,i)1
否则的话,假如整个序列一个循环节中出现连续超过k个1,那么第一个循环节内就出现连续超过k个1。如果两个循环节交交界处出现连续超过k个1,那么第一个循环节首尾相接就会出现连续超过k个1。
所以,只需要考虑一个循环节即可!
我们先预处理f[i]表示长度为i的序列,填0/1,不能有连续的超过k个1,有多少种方案。
那么 f[i]=f[i1]2f[ik2]
其中f初值是f[-1]=f[0]=1,其余f都为0
这个方程不难理解,在长度为i-1的基础上,末尾添0/1。
此时,非法的情况只有末尾出现k+1个1,然后这k+1个1最前有一个0(必须有这个0,因为f[i-1]说明了不添最后一个1只有k个1,因为f[i-1]合法,所以最前面必须是0,当然,也有只有k+1个1的情况,最前面没有任何东西,所以还有f[-1]=1)
求出f之后,怎么求g?
我们当然可以先让g[i]=f[(n,i)],但是会算多。因为f并没有考虑首尾相接的情况。
于是我们尝试减去首尾相接会超的情况。
枚举右端连续1的个数为j,1<=j<=k,显然这j个1前还有有一个0
那么左端至少有k-j+1个连续1,最多能有多少个呢?最多只能有k个。
但是还有注意,现在 k<n ,我们不能让左右端加起来超过(n,i),所以左端至多只能有(n,i)-k-2个
右端下限确定了,我们来枚举增量l。
min(j1,(n,i)k2)l=0f[(n,i)kl3]
嘿嘿,右边那个玩意与j无关,因此可以预处理一下前缀和。
然后再枚举j,加上去即可。
不懂看一下代码

#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=2000+10,mo=100000007;
int len[maxn],g[maxn],f[maxn],two[maxn],num[maxn];
int i,j,k,l,r,t,n,m,ca,ans,ni;
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
int quicksortmi(int x,int y){
    if (!y) return 1;
    int t=quicksortmi(x,y/2);
    t=(ll)t*t%mo;
    if (y%2) t=(ll)t*x%mo;
    return t;
}
void getnum(int n){
    int l;
    if (n-k-3<0) num[0]=1;
    else num[0]=f[n-k-3];
    fo(l,1,min(k-1,n-k-2)){
        num[l]=num[l-1];
        if (n-k-l-3<0) num[l]++;
        else (num[l]+=f[n-k-l-3])%=mo;
    }
}
int main(){
    freopen("t22.in","r",stdin);freopen("t2.ans","w",stdout);
    scanf("%d",&ca);
    while (ca--){
        scanf("%d%d",&n,&k);
        fo(i,1,n) len[i]=gcd(n,i);
        f[0]=1;
        fo(i,1,n){
            f[i]=(ll)f[i-1]*2%mo;
            if (i==k+1) f[i]--;
            else if (i>k+1) (f[i]-=f[i-k-2])%=mo;
        }
        if (k>=n){
            fo(i,1,n){
                t=len[i];
                g[i]=f[t];
            }
            ni=quicksortmi(n,mo-2);
            ans=0;
            fo(i,1,n) (ans+=g[i])%=mo;
            ans=(ll)ans*ni%mo;
            (ans+=mo)%=mo;
            printf("%d\n",ans);
            continue;
        }
        else if (k==1){
            fo(i,1,n){
                t=len[i];
                if (t==1) g[i]=1;
                else{
                    g[i]=f[t];
                    if (t==3) g[i]--;
                    else g[i]-=f[t-4];
                }
            }
            ni=quicksortmi(n,mo-2);
            ans=0;
            fo(i,1,n) (ans+=g[i])%=mo;
            ans=(ll)ans*ni%mo;
            (ans+=mo)%=mo;
            printf("%d\n",ans);
            continue;
        }
        fo(i,1,n){
            t=len[i];
            if (t<=k) g[i]=f[t]-1;
            else if (t==k+1) g[i]=f[t];
            else{ 
                getnum(t);
                r=0;
                fo(j,1,k) (r+=num[min(j-1,t-k-2)])%=mo;
                g[i]=(f[t]-r)%mo;
            } 
        }
        ni=quicksortmi(n,mo-2);
        ans=0;
        fo(i,1,n) (ans+=g[i])%=mo;
        ans=(ll)ans*ni%mo;
        (ans+=mo)%=mo;
        printf("%d\n",ans);
    }
}

你可能感兴趣的:(置换,一般动规与递推)