burnside/polya学习小计

前言

终于学会了
我好菜啊

问题

比如给出一个环,环上有n个点,每个点可以染一种颜色,共m种不同的颜色,求在循环同构下的不同方案数
不会burnside的话连暴力都不好写

burnside

一些定义
置换:指一次或多次操作之和,类似矩乘中的转移矩阵
不定点:若一种方案在某个置换下等于原方案,则称这种置换是这个方案的不定点
在这里插入图片描述
如图是n=4,m=2的情况,可以发现一共有16种染色情况和4种置换(就是n种)
但仔细观察发现本质只有(1)(2)(3,4,5,6)(7,8,9,10)(11,12)(13,14,15,16)(括号内为本质相同的一种)共6种情况

如何快速计算不同方案?
考虑把本质相同的几种情况归为一种,即对答案贡献为1
首先设一类置换的循环节长度为c,则循环节个数为n/c,本质相同的一共有c种(在一种的基础上左右旋转),每一种的不定点个数为n/c(把循环节轮换,共n/c个)
因为c并不好求,所以考虑直接算出答案
可以发现本质相同个数*不定点个数=n,对最终答案/n就是总方案
由于每种置换都算了所有情况的不定点,所以对于c种本质相同的方案都会算一次不定点,因此不同方案l等于
l = ∑ C ( a i ) n l=\frac{\sum{C(ai)}}{n} l=nC(ai)(C(i)表示方案i的不定点个数)
对于一般情况:
在这里插入图片描述,其中G为置换集

polya

然而。。。
如果要计算不定点C(ai)的话,那就要枚举n^m种情况并计算不动点
但这样还不如暴力
所以就需要polya定理来提供具体的计算方式
对于一种置换可以被分成若干个循环节(即从一个位置出发走一圈走回权威,循环节之间相邻),如果要为不定点则每个循环节内点的颜色都要各自相同
所以第i种置换设有bi个循环节
l = ∑ m b i n l=\frac{\sum{m^{bi}}}{n} l=nmbi
而在这个问题中,bi=gcd(n,i)
证明考虑n和i互质时,则置换一定可以选择完1~n中的所有数,故为1
如果把n和i同时扩大,则bi一定会扩大同样的倍数
感性理解

如上面那个问题,bi分别等于4(初始状态)、1、2、1,则方案l=(24+21+22+21)/4=6

例题

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

Input
第一行有一个数T,表示以下有T组数据,每组数据有两个整数N,K。
每组数据之间有一个空行隔开。

Output
输出T行,每行顺次对应一组测试数据。
每组数据你需要输出最后的方案数除以100000007的余数。

Sample Input
3
3 1
3 3
4 1

Sample Output
2
4
3
解释:
第一组数据的方案是:MMM,MMW (M是男孩, W是女孩)。
第二组数据的方案是:MMM,MMW,MWW,WWW。
第三组数据的方案是:MMMM, MMMW,MWMW。

Data Constraint
对于20%的数据T<=20;
对于100%的数据T<=50;
对于20%的数据N,K<=20;
对于100%的数据N,K<=2000;

题解

如果没有k的限制,那就可以直接套式子
但加上k的限制就不能直接用了,因为可能有>k个循环节坐了女生
可以发现,如果每个循环节第一个位置组成的序列是合法的,那么剩下的也都会合法
所以只需要考虑前(n,i)个
设f[i]表示长度为i且首位坐男生,而且包含所有男生合法方案
转移就枚举倒数第二个男生,注意中间的女生不能超过k
处理完f就好搞了,考虑还剩下多少个女生(同样不能超过k),贡献就是f[循环节个数-剩下女生个数]*(剩下女生个数+1)(因为可以旋转)

当然还可以用前缀和优化然而貌似不用也可以过

其它题解的传送门:
https://www.cnblogs.com/DUXT/p/5958205.html
https://blog.csdn.net/WerKeyTom_FTD/article/details/52651361

code

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define mod 100000007
#define Mod 100000005
#define N 2000
using namespace std;

long long f[N+1];
int T,n,i,j,k;
long long s,ans;

long long qpower(long long a,int b)
{
	long long ans=1;
	
	while (b)
	{
		if (b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	
	return ans;
}

void init()
{
	f[1]=1;
	f[2]=1;
	
	fo(i,3,n)
	{
		f[i]=0;
		fo(j,max(i-k-1,1),i-1)
		f[i]=(f[i]+f[j])%mod;
	}
}

int gcd(int n,int m)
{
	int r=n%m;
	
	while (r)
	{
		n=m;
		m=r;
		r=n%m;
	}
	
	return m;
}

int main()
{
	for (scanf("%d",&T); T; --T)
	{
		scanf("%d%d",&n,&k);
		ans=0;
		
		if (n<=k)
		{
			fo(i,1,n)
			ans=(ans+qpower(2,gcd(n,i)))%mod;
		}
		else
		{
			init();
			fo(i,1,n)
			{
				s=gcd(n,i);
				fo(j,max(s-k,1),s)
				ans=(ans+f[j]*(s-j+1))%mod;
			}
		}
		
		printf("%lld\n",ans*qpower(n,Mod)%mod);
	}
}

参考资料

https://blog.csdn.net/liangzhaoyang1/article/details/72639208
https://www.cnblogs.com/beisong/p/4767858.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值