前言
这是一道Burnside引理的应用题
题目相关
题目大意
一个长度为 n n n的 01 01 01圈,没有超过 k k k个的连续的 1 1 1,求方案数
数据范围
T ≤ 50 , n ≤ 2000 , k ≤ 2000 T\le50,n\le2000,k\le2000 T≤50,n≤2000,k≤2000
题解
我们设
f
i
f_i
fi表示不含超过
k
k
k个连续
1
1
1的长度为
i
i
i的链的数量
我们考虑求
f
i
f_i
fi,用所有方案减去不合法方案数,对于一个长度为
i
−
1
i-1
i−1的不合法方案,其后面再加上一个数字,其依然不合法,然后我们考虑因为加入最后一位而不合法的串,一定是最后
k
+
1
k+1
k+1位是
1
1
1,倒数第
k
+
2
k+2
k+2位是
0
0
0,并且其它位合法,那么直接递推即可
我们设
h
i
h_i
hi表示长度为
i
i
i、包含至少一个
0
0
0、不含超过连续
k
k
k个
1
1
1的环的数量(固定
1
1
1号点)
先不说要如何计算
我们考虑答案,我们列出Burnside引理的式子
l
=
1
∣
G
∣
∑
a
i
∈
G
c
1
(
a
i
)
l=\frac{1}{|G|}\sum_{a_i\in G}c_1(a_i)
l=∣G∣1ai∈G∑c1(ai)
我们发现对于所有置换(即所有
0
≤
i
<
n
0\le i<n
0≤i<n,转动
i
i
i次),其
c
(
a
i
)
c(a_i)
c(ai)刚好是最小整除周期是
i
i
i的因数的方案数,实际操作的时候我们发现
c
(
a
i
)
=
h
g
c
d
(
i
,
n
)
c(a_i)=h_{gcd(i,n)}
c(ai)=hgcd(i,n)
我们考虑如何求
h
i
h_i
hi,我们发现我们可以在一条链的两端连上
01
⋅
⋅
⋅
10
01···10
01⋅⋅⋅10形式的一串(其中
1
1
1的数量要小于等于
k
k
k),我们枚举
1
1
1的数量至少为几,我们再枚举接入的位置,我们发现,插入的元素越多,方案越多
我们设原串为
X
X
X,插入的
i
i
i个
1
1
1在头与尾的串分别为
A
A
A和
B
B
B,我们发现,最后一定是“
A
0
X
0
B
A0X0B
A0X0B”的形式,我们发现若
1
1
1的个数为
i
i
i,那么就有
i
+
1
i+1
i+1种方案
那么我们考虑对
f
f
f进行前缀和,并且对于所有
i
i
i直接转移选
i
i
i~
k
k
k个的方案,那么
i
i
i个的方案就会被转移到
i
+
1
i+1
i+1次,即可
我们发现只有
i
i
i是
n
n
n的因数时,
h
i
h_i
hi是要求的,直接
d
p
dp
dp一个
h
i
h_i
hi在
i
≤
k
i\le k
i≤k的时候是
O
(
1
)
\mathcal O(1)
O(1)的,
i
>
k
i>k
i>k的时候是
O
(
k
)
\mathcal O(k)
O(k)的,那么总复杂度为
O
(
∑
i
>
k
,
i
∣
n
k
)
\mathcal O\left(\sum_{i>k,i|n}k\right)
O⎝⎛i>k,i∣n∑k⎠⎞
我们设大于
k
k
k且是
n
n
n的因数的数有
x
x
x个,容易发现
n
n
n的因数从大到小排,第
j
j
j个的值小于等于
n
j
\frac nj
jn,那么
k
≤
n
x
k\le\frac nx
k≤xn,那么
x
∗
k
≤
n
x*k\le n
x∗k≤n,即直接
d
p
dp
dp是
O
(
n
)
\mathcal O(n)
O(n)的
另外,我们发现对于所有 c ( a i ) c(a_i) c(ai)的求和可以枚举j=gcd(i,n),那么贡献次数就是 φ ( n i ) \varphi(\frac ni) φ(in),我们可以通过线性筛筛 φ \varphi φ,并且枚举 j j j,这样就可以做到 O ( n ) \mathcal O(n) O(n)的复杂度
综上,总复杂度 O ( n ) \mathcal O(n) O(n)
代码
#include<bits/stdc++.h>
typedef long long ll;
#define rg register
template <typename T> inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template <typename T> inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template <typename T> inline void print(const T x){if(x<0)putchar('-'),printe(-x);else printe(x);}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
const int maxn=2001;
const ll mod=100000007;
inline int pow(int x,int y)
{
int res=1;
for(;y;y>>=1,x=(ll)x*x%mod)if(y&1)res=(ll)res*x%mod;
return res;
}
inline void md(int&x){if(x>=mod)x-=mod;}
inline int Md(const int x){return x>=mod?x-mod:x;}
int n,k,bit[maxn],F[maxn+5],*f=F+5,h[maxn],sum;
ll T,ans;
bool isprime[maxn];int prime[maxn],primesize,phi[maxn];
inline void getphi(const int size)
{
phi[1]=1;
memset(isprime,1,sizeof(isprime));
for(rg int i=2;i<=size;i++)
{
if(isprime[i])prime[++primesize]=i,phi[i]=i-1;
for(rg int j=1;j<=primesize&&(ll)prime[j]*i<=size;j++)
{
isprime[prime[j]*i]=0;
if(i%prime[j]==0)
{
phi[prime[j]*i]=phi[i]*prime[j];
break;
}
phi[prime[j]*i]=phi[i]*(prime[j]-1);
}
}
}
int main()
{
// freopen("party.in","r",stdin),freopen("party.out","w",stdout);
bit[0]=1;
for(rg int i=1;i<=2000;i++)bit[i]=Md(bit[i-1]<<1);
getphi(2000);
read(T);
while(T--)
{
read(n),read(k);
if(k>n)k=n;
sum=0,ans=0;
memset(F,0,sizeof(F));
memset(h,0,sizeof(h));
for(rg int i=0;i<=k;i++)f[i]=bit[i];
f[-1]=1;
for(rg int i=k+1;i<=n;i++)
{
sum=Md(Md(sum<<1)+f[i-k-2]);
f[i]=Md(bit[i]-sum+mod);
}
for(rg int i=0;i<=n;i++)f[i]=Md(f[i]+f[i-1]);
for(rg int i=1;i<=n;i++)
if(n%i==0)
{
if(i<=k)h[i]=bit[i]-1;
else for(rg int j=0;j<=k;j++)h[i]=Md(Md(h[i]+f[i-j-2])+mod-f[i-k-3]);
ans+=(ll)h[i]*phi[n/i]%mod;
}
print(((ans%mod+mod)*pow(n,mod-2)+(n==k))%mod),putchar('\n');
}
return 0;
}
总结
Burnside引理知道就好
主要是要求的东西会求
优化复杂度大法好