题目大意
n
个人围绕着圆桌坐着,其中一些是男孩,另一些是女孩。你的任务是找出所有合法的方案数,使得不超过
循环同构会被认为是同一种方案。
一个测试点
T
个数据。
题目分析
处理如循环同构的等价类计数问题, Burnside′s 引理无疑是一个强有力的方法。
- Burnside′s 引理
-
n=∑g∈GC(g)|G|
,其中,
G
是在一个目标集上的置换群,
C(g) 表示目标集在置换 g 的作用下的不动点个数。
没有学过
如何统计不动点呢?参考[SDOI2013]项链一题,我们考虑旋转
i
位
这个怎么求呢?我们先不考虑头尾相接的情况,令
注意当 f−1 要设置为 1 ,原因就是一些简单的边界情况,读者自行思考吧。这个你不怕内存位置出错可以像Werkeytom_FTD一样作死直接设置,但最好还是特判一下。后面的计算也会有用到
计算出这个以后,我们就要加上两端相连的情况了。枚举右端的连续 1 的个数
可以发现后面一个求和可以预处理前缀和来解决。
这样就完了?当然不是,我们来考虑一些特殊情况:
- n≤k ,这个时候前 gcd(i,n) 个位置填什么都可以,因此贡献应当是 2gcd(i,n) 。
-
gcd(i,n)≤k<n
,这个时候全部填
1
相对于
gcd(i,n) 的长度是合法的,但是相对于整个序列是不合法的,因此贡献要减 1 。 - 其余情况正常处理。
总而言之,细节还是挺多的。需要思路清晰才能切出来。
时间复杂度奇技淫巧特殊的预处理技巧,程序跑得飞快。
代码实现
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int P=100000007;
const int N=2000;
int f[N+50],sum[N];
int R,ans,T,n,k;
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int quick_power(int x,int y)
{
int ret=1;
for (;y;y>>=1,x=1ll*x*x%P) if (y&1) ret=1ll*ret*x%P;
return ret;
}
void pre()
{
f[0]=1;
for (int i=1;i<=n;i++) f[i]=((f[i-1]<<1)-(i-k-2>=0?f[i-k-2]:(!(i-k-1)?1:0))+P)%P;
sum[0]=1;
for (int i=1;i<=n;i++) sum[i]=(sum[i-1]+f[i])%P;
}
int g(int d,int r)
{
int ret=((d-k-3>=0?sum[d-k-3]:0)-(d-k-r-2>=0?sum[d-k-r-3]:0)+P)%P;
if (d-k>=2&&d-k-2<r) (ret+=1)%=P;
return ret;
}
int calc(int d)
{
if (k>=n) return quick_power(2,d);
int ret=0;
for (int r=1;r<=k;r++) (ret+=g(d,r))%=P;
ret=(f[d]-ret+P)%P;
if (k>=d) (ret+=P-1)%=P;
return ret;
}
void solve()
{
ans=0;
for (int l=0;l<n;l++) (ans+=calc(gcd(l,n)))%=P;
R=quick_power(n,P-2),ans=1ll*ans*R%P;
}
int main()
{
freopen("party.in","r",stdin),freopen("party.out","w",stdout);
for (scanf("%d",&T);T--;)
{
scanf("%d%d",&n,&k);
pre(),solve();
printf("%d\n",ans);
}
fclose(stdin),fclose(stdout);
return 0;
}