LYK loves girls(DP,Burnside 定理)

题意简述

一个环上有 N 个位置,每个位置要么是男要么是女,规定不能有超过连续 K 个女孩相邻。问本质不同的合法环有多少。循环同构被认为是相同。环中必须至少有一个男的。

1 <= K <= N <= 1e5

题解

这道题其实是比较经典的一种 Burnside 应用题。

根据 Burnside 定理:

a n s = 1 ∣ G ∣ ∑ g ∈ G λ ( g ) ans=\frac{1}{|G|}\sum_{g∈G}λ(g) ans=G1gGλ(g)

其中 λ ( g ) λ(g) λ(g) 是置换 g g g 的不动点个数,换句话说,是只考虑置换 g g g 时,变换后本质不变的方案数。这道题的 g g g 即旋转的位数,如果向右旋转 i i i 位,整个环本质不变的话,就相当于相邻的长度为 i i i 的段相同,然而环的总长为 n n n ,所以相当于相邻长度为 g c d ( n , i ) gcd(n,i) gcd(n,i) 的段相同。

然后我们再尝试求不考虑循环同构的情况下,长度为 1~n 的环的方案数。有 K 的限制,我们可以 DP 做。官方题解给了个极好的 DP :

令 dp[i] 表示有 i 个人,且最后一个人是男的方案总数。
那么一个递推式是:
d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] + . . . + d p [ i − k − 1 ] dp[i] = dp[i-1] + dp[i-2] + ... + dp[i-k-1] dp[i]=dp[i1]+dp[i2]+...+dp[ik1]
这个 dp 是可以通过前缀和加速的。
这样就可以先固定最后一个是男的,然后考虑前面,
假设一开始有连续y个女的,且第y+1个人是男的,那么前y个人是可以任意旋转的(共y+1种可能),对答案的贡献为 ( y + 1 ) ∗ d p [ i − y − 1 ] (y+1)*dp[i-y-1] (y+1)dp[iy1](通过这个公式应该可以理解上面那句话)。

这个是 O ( n 2 ) O(n^2) O(n2) 的,还可以优化:

对于第i种置换,有用的数仅仅是 gcd(i,n) 种,也就是说我们只需处理 n 的所有约数就可以了。
时间复杂度为 tot*n 。(其中tot表示n的约数个数)

直白的说,复杂度为 O ( n n ) O(n\sqrt n) O(nn ) ,大多数人应该都这么做的吧。


这里有个 O ( 5 n ) O(5n) O(5n) 的解法不知当讲不当讲?😉

考虑上面的 DP 告诉我们不考虑循环同构的情况下,长度为 i 的环的方案数是

a n s [ i ] = ∑ y = 0 k d p [ i − y − 1 ] ∗ ( y + 1 ) ans[i]=\sum_{y=0}^k dp[i-y-1]*(y+1) ans[i]=y=0kdp[iy1](y+1)

假设我们求出这个值了,怎么过渡到 i + 1 i+1 i+1 呢?

我们推一下式子:

a n s [ i + 1 ] = ∑ y = 0 k d p [ i − y ] ∗ ( y + 1 ) ans[i+1]=\sum_{y=0}^k dp[i-y]*(y+1) ans[i+1]=y=0kdp[iy](y+1)
                   = ∑ y = 0 k − 1 d p [ i − y − 1 ] ∗ ( y + 2 ) + d p [ i ] ~~~~~~~~~~~~~~~~~~=\sum_{y=0}^{k-1} dp[i-y-1]*(y+2)+dp[i]                   =y=0k1dp[iy1](y+2)+dp[i]
                   = ∑ y = 0 k − 1 d p [ i − y − 1 ] ∗ ( y + 1 ) + ∑ y = 0 k − 1 d p [ i − y − 1 ] + d p [ i ] ~~~~~~~~~~~~~~~~~~=\sum_{y=0}^{k-1} dp[i-y-1]*(y+1)+\sum_{y=0}^{k-1}dp[i-y-1]+dp[i]                   =y=0k1dp[iy1](y+1)+y=0k1dp[iy1]+dp[i]
                   = ∑ y = 0 k − 1 d p [ i − y − 1 ] ∗ ( y + 1 ) + ∑ y = 0 k d p [ i − y ] ~~~~~~~~~~~~~~~~~~=\sum_{y=0}^{k-1} dp[i-y-1]*(y+1)+\sum_{y=0}^{k}dp[i-y]                   =y=0k1dp[iy1](y+1)+y=0kdp[iy]
                   = ∑ y = 0 k d p [ i − y − 1 ] ∗ ( y + 1 ) − d p [ i − k − 1 ] ∗ ( k + 1 ) + ∑ y = 0 k d p [ i − y ] ~~~~~~~~~~~~~~~~~~=\sum_{y=0}^{k} dp[i-y-1]*(y+1)-dp[i-k-1]*(k+1)+\sum_{y=0}^{k}dp[i-y]                   =y=0kdp[iy1](y+1)dp[ik1](k+1)+y=0kdp[iy]
                   = a n s [ i ] − d p [ i − k − 1 ] ∗ ( k + 1 ) + ∑ y = 0 k d p [ i − y ] ~~~~~~~~~~~~~~~~~~=ans[i]-dp[i-k-1]*(k+1)+\sum_{y=0}^{k}dp[i-y]                   =ans[i]dp[ik1](k+1)+y=0kdp[iy]

其中 ∑ y = 0 k d p [ i − y ] \sum_{y=0}^kdp[i-y] y=0kdp[iy] 可以前缀和优化,于是我们就推出了 O ( n ) O(n) O(n) a n s [ . . . ] ans[...] ans[...] 的方法。

最后求 1 n ∑ i = 1 n a n s [ g c d ( n , i ) ] \frac1n\sum_{i=1}^nans[gcd(n,i)] n1i=1nans[gcd(n,i)] 只需要每个求个 gcd ,gcd 的复杂度是 O ( lg ⁡ m i n ( i , n ) ) O(\lg min(i,n)) O(lgmin(i,n)) 的,因此总复杂度为 O ( n lg ⁡ n ) O(n\lg n) O(nlgn) 即最大为 O ( 5 n ) O(5n) O(5n)

CODE

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define SI set<cp>::iterator
#define lowbit(x) (-(x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
int dp[MAXN],sum_[MAXN];
int inv[MAXN],as[MAXN];
int gcd(int a,int b) {return b==0 ? a:gcd(b,a%b);}
int main() {
	freopen("girls.in","r",stdin);
	freopen("girls.out","w",stdout);
	n = read();k = read();
	dp[0] = 1;sum_[0] = 0;
	inv[0] = inv[1] = 1;
	for(int i = 1;i <= n;i ++) {
		sum_[i] = (sum_[i-1] + dp[i-1]) % MOD;
		dp[i] = (sum_[i] +MOD- sum_[max(0,i-k-1)]) % MOD;
		if(i-1) inv[i] = (MOD-inv[MOD%i]) *1ll* (MOD/i) % MOD;
	}
	int sm1 = 0,sm2 = 1,sm3 = 0;
	for(int i = 1;i <= n;i ++) {
		(sm1 += sm2) %= MOD;
		as[i] = sm1;
		(sm2 += dp[i]) %= MOD;
		if(i >= k+1) (sm2 += MOD-dp[i-k-1]) %= MOD,(sm1 += MOD-(dp[i-k-1] *1ll* (k+1) % MOD)) %= MOD;
		(sm3 += as[gcd(i,n)]) %= MOD;
	}
	sm3 = sm3 *1ll* inv[n] % MOD;
	printf("%d\n",sm3);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值