zoukankan      html  css  js  c++  java
  • [BZOJ1547]周末晚会:Burnside引理+DP

    分析

    Attention!这道题的模数是(1e8+7)

    注意到循环同构会被认为是同一种方案,我们可以把顺时针旋转每个人的位置作为置换,容易发现这些置换一定会形成一个置换群,于是题目所求的所有合法的方案数便是这个置换群下等价类的数目,可以使用Burnside引理解决。

    考虑如何求在置换“顺时针旋转(x)位置”下不动点的数目,可以发现这样的不动点的男女排列一定具有周期性,且(gcd(n,x))是它的一个周期,这里可以自行画图理解一下。这样我们又再次简化了题目,我们现在只需考虑这一个周期了。

    (d=gcd(n,x)),首先进行如下的分类讨论:

    1. (n leq k),这样不存在不合法的方案,返回(2^d)

    2. (n>k)(d leq k),这样只要这个周期内不全是女生即是合法方案,返回(2^d-1)

    3. (n>k)(d>k),DP解决。

    怎么DP?

    (f[i][j])表示不考虑循环的情况下,考虑到前(i)个人,最后(j)个人是女生的方案数。

    (g[i][j])表示不考虑循环的情况下,考虑到前(i)个人,保证第一个人是男生,最后(j)个人是女生的方案数。

    显然有转移:

    [f[i][0]=sum_{x=0}^{min(i-1,k)}f[i-1][x] ]

    [f[i][j]=f[i-1][j-1] (j eq 0) ]

    (g[i][j])的转移与(f[i][j])类似。

    于是对于长度为(i)的区间,其合法方案数为(sum_{j=1}^{min(i,k)}f[i][j]-sum_{j=k+1}^{min(i-1,2k)}g[i-j][0] imes (2k-j+1)),这个可以直接预处理出来。

    总结一下上面所提到的,在置换“顺时针旋转(x)位置”下的不动点,男女排列一定具有周期性,(d=gcd(n,x))是其一个周期,我们只需要对这个区间进行讨论和计算,并统计到所有置换的不动点个数和中即可。

    考虑到这样一个事实,(sum_{i=1}^{n}[gcd(i,n)=d]=varphi(frac{n}{d})),所以可以直接枚举(n)的约数(d),然后计算(d)对答案的贡献。(这一步可有可无)

    最后根据Burnside引理乘上一个(n^{-1})

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <cctype>
    #include <algorithm>
    #define rin(i,a,b) for(int i=(a);i<=(b);i++)
    #define rec(i,a,b) for(int i=(a);i>=(b);i--)
    #define trav(i,x) for(int i=head[(x)];i;i=e[i].nxt)
    using std::cin;
    using std::cout;
    using std::endl;
    typedef long long LL;
    
    inline int read(){
    	int x=0;char ch=getchar();
    	while(ch<'0'||ch>'9') ch=getchar();
    	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    	return x;
    }
    
    const int MAXN=2005;
    const int MOD=1e8+7;
    int n,k;
    LL f[MAXN][MAXN],g[MAXN][MAXN],cnt[MAXN];
    int prm[MAXN],phi[MAXN],pcnt;
    bool vis[MAXN];
    
    inline LL qpow(LL x,LL y){
    	LL ret=1,tt=x%MOD;
    	while(y){
    		if(y&1) ret=ret*tt%MOD;
    		tt=tt*tt%MOD;
    		y>>=1;
    	}
    	return ret;
    }
    
    inline void pre_process1(){
    	phi[1]=1;
    	rin(i,2,n){
    		if(!vis[i]){
    			prm[++pcnt]=i;
    			phi[i]=i-1;
    		}
    		for(int j=1;j<=pcnt&&i*prm[j]<=n;j++){
    			vis[i*prm[j]]=1;
    			if(i%prm[j]==0){
    				phi[i*prm[j]]=phi[i]*prm[j];
    				break;
    			}
    			phi[i*prm[j]]=phi[i]*(prm[j]-1);
    		}
    	}
    }
    
    inline void pre_process2(){
    	f[0][0]=f[1][0]=f[1][1]=g[1][0]=1;
    	rin(i,2,n){
    		f[i][0]=g[i][0]=0;
    		rin(j,0,std::min(i-1,k)){
    			f[i][0]+=f[i-1][j];
    			if(f[i][0]>=MOD) f[i][0]-=MOD;
    		}
    		rin(j,1,std::min(i,k)) f[i][j]=f[i-1][j-1];
    		rin(j,0,std::min(i-2,k)){
    			g[i][0]+=g[i-1][j];
    			if(g[i][0]>=MOD) g[i][0]-=MOD;
    		}
    		rin(j,1,std::min(i-1,k)) g[i][j]=g[i-1][j-1];
    	}
    	rin(i,1,n){
    		cnt[i]=0;
    		rin(j,0,std::min(i,k)){
    			cnt[i]+=f[i][j];
    			if(cnt[i]>=MOD) cnt[i]-=MOD;
    		}
    		rin(j,k+1,std::min(i-1,k<<1)){
    			cnt[i]-=g[i-j][0]*((k<<1)-j+1)%MOD;
    			if(cnt[i]<0) cnt[i]+=MOD;
    		}
    	}
    }
    
    inline LL solve(int x){
    	if(x<=k){
    		if(k<n) return (qpow(2,x)-1+MOD)%MOD;
    		else return qpow(2,x);
    	}
    	else return cnt[x];
    }
    
    int main(){
    	int T=read();
    	n=2000;
    	pre_process1();
    	while(T--){
    		n=read(),k=read();
    		pre_process2();
    		int lim=sqrt(n);
    		LL ans=0;
    		rin(i,1,lim){
    			if(n%i) continue;
    			ans=(ans+solve(i)*phi[n/i])%MOD;
    			if(i*i==n) continue;
    			ans=(ans+solve(n/i)*phi[i])%MOD;
    		}
    		printf("%lld
    ",ans*qpow(n,MOD-2)%MOD);
    	}
    	return 0;
    }
    
  • 相关阅读:
    CMake命令笔记
    在Win10上,Android Studio检测不到设备的解决方案
    在MFC中使用大漠插件
    Win10更新后,IE和Edge以外的浏览器打开网页速度慢的解决方案
    jsp的自定义标签
    js和JQuery区别
    POI (Apache POI)
    TCP程序设计基础
    传参在mybatis的sql映射文件中正确获取
    I/O(输入/输出)
  • 原文地址:https://www.cnblogs.com/ErkkiErkko/p/10067750.html
Copyright © 2011-2022 走看看