【Acwing】 3134. 魔法手链

文章介绍了如何应用Burnside引理来计算环状排列中旋转不变的子集数量。通过矩阵乘法优化,处理大数情况下计算不动点的方案数,最后给出了一段C++代码实现该算法。
摘要由CSDN通过智能技术生成

题目链接

点击打开链接

题目解法

有旋转相同的题首先考虑置换
考虑 B u r n s i d e Burnside Burnside 引理,需要求出不动点的个数
考虑一个点 x x x,每次往后跳 k k k 个(即旋转 k k k ),跳回 x x x 需要至少跳 t t t
可得 x + k t ≡ x ( m o d    n ) x+kt\equiv x(mod\;n) x+ktx(modn)
t t t 的最小值为 n d \frac{n}{d} dn,其中 d = ( n , k ) d=(n,k) d=(n,k)
所以说当前置换环上有 t t t n d \frac{n}{d} dn 个点
因为每个点能到的点的形式均为 x + t k x+tk x+tk x + t ′ d x+t'd x+td
这样的点有 n d \frac{n}{d} dn
因为每个点都不相同,所以每个点跳出的点相隔 d d d
根据 b u r n s i d e burnside burnside 引理,不动点是 x x x x + d , . . . x+d,... x+d,... 颜色都相同的点
所以只要考虑 x , x + 1 , . . . , x + d − 1 x,x+1,...,x+d-1 x,x+1,...,x+d1,且 x x x 不等于 x + d − 1 x+d-1 x+d1 的颜色的方案数
d p i , j dp_{i,j} dpi,j 表示当前 i i i 点的颜色为 j j j 的方案数
由于是一个环,所以需要固定 x x x 的颜色
因为 n n n 很大,所以需要矩阵乘法优化 d p dp dp
且不能枚举每个 k k k,考虑枚举 d d d
那么需要知道 ( k d , n d ) = 1 (\frac{k}{d},\frac{n}{d})=1 (dk,dn)=1 k k k 的个数,即为 ϕ ( n d ) \phi(\frac{n}{d}) ϕ(dn)
时间复杂度 O ( 1600 m 4 l o g    n ) O(1600m^4log\;n) O(1600m4logn),其中 1600 1600 1600 i n t int int 范围内整数的最多因数个数
时间不够
考虑把 d p dp dp 状态扩充为 3 维,再记录一维第一个点的颜色,矩乘的时间仍是 O ( m 3 l o g    n ) O(m^3log\;n) O(m3logn),但可以减少枚举第一个点颜色的 m m m
时间复杂度 O ( 1600 m 3 l o g    n ) O(1600m^3log\;n) O(1600m3logn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int M(15),P(9973);
struct Matrix{
	int n,m,a[M][M];
}Bas,f;
Matrix operator *(const Matrix A,const Matrix B){
	Matrix C;C.n=A.n,C.m=B.m;
	memset(C.a,0,sizeof(C.a));
	for(int i=1;i<=C.n;i++) for(int j=1;j<=C.m;j++) for(int k=1;k<=A.m;k++) C.a[i][j]=(C.a[i][j]+(LL)A.a[i][k]*B.a[k][j])%P;
	return C;
}
int n,m,k;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int getphi(int x){
	int res=x;
	for(int i=2;i*i<=x;i++)
		if(x%i==0){
			while(x%i==0) x/=i;
			res=res/i*(i-1);
		}
	if(x>1) res=res/x*(x-1);
	return res;
}
int solve(int d){
	int totk=getphi(n/d);
	int res=0;
//	cout<<d<<' '<<totk<<'\n';
	memset(f.a,0,sizeof(f.a));
	f.n=m,f.m=m;
	for(int i=1;i<=m;i++) f.a[i][i]=1;
	Matrix mul=Bas;
	int tn=d-1;
	for(;tn;tn>>=1){
		if(tn&1) f=f*mul;
		mul=mul*mul;
	}
	for(int i=1;i<=m;i++)
//	cout<<f.a[1][1]<<' '<<f.a[1][2]<<' '<<f.a[1][3]<<'\n'; 
		for(int j=1;j<=m;j++) if(Bas.a[i][j]) (res+=f.a[i][j])%=P;
//		cout<<res<<'\n';
	return (LL)res*totk%P;
}
int qmi(int a,int b,int p){
	int res=1;
	for(;b;b>>=1){
		if(b&1) res=(LL)res*a%P;
		a=(LL)a*a%p;
	}
	return res;
} 
void work(){
	n=read(),m=read(),k=read();
	Bas.n=m,Bas.m=m;
	for(int i=1;i<=m;i++) for(int j=1;j<=m;j++) Bas.a[i][j]=1;
	for(int i=1,a,b;i<=k;i++) a=read(),b=read(),Bas.a[a][b]=Bas.a[b][a]=0;
	int ans=0;
	for(int i=1;i*i<=n;i++)
		if(n%i==0){
			ans=(ans+solve(i))%P;
			if(i*i!=n) ans=(ans+solve(n/i))%P;
		}
//	cout<<ans<<'\n';
	printf("%d\n",(LL)ans*qmi(n,P-2,P)%P);
}
int main(){
	int T=read();
	while(T--) work();
	return 0;
}
/*
f[i][j]+=f[i-1][k]
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值