【ybt金牌导航8-5-7】魔法手镯(Burnside 引理)(矩阵乘法优化DP)

魔法手镯

题目链接:ybt金牌导航8-5-7

题目大意

给你一个长度为 n 的珠子环,然后规定一些珠子不能相邻。
然后有多少种本质不同的珠子环。
本质相同时指它可以通过旋转变成一样的。

思路

看到循环置换,和颜色,不难想到 Polya 定理。

但是它有限制条件,那我们就考虑用 Burnside 引理。
然后你发现它的限制条件你可以按长度 DP 下去:
f i , j f_{i,j} fi,j 为一开始的颜色为 i i i,最后的颜色为 j j j 的方案数。

然后你发现这个 DP 可以用矩阵乘法优化。

然后你就直接枚举 gcd ⁡ ( i , n ) \gcd(i,n) gcd(i,n) 的结果,每个都求出来结果乘上 ϕ \phi ϕ 再最后除 n n n 就是答案了。

代码

#include<cstdio>
#define mo 9973

using namespace std;

struct matrix {
	int n, m;
	int a[11][11];
}a, one, A;
int T, n, m, k, x, y, ans;

int jia(int x, int y) {
	return x + y >= mo ? x + y - mo : x + y;
}

int mul(int x, int y) {
	int re = x * y;
	if (re > mo) re = re - re / mo * mo;
	return re;
}

matrix operator *(matrix x, matrix y) {//矩阵乘法
	matrix re;
	re.n = x.n; re.m = y.m;
	for (int i = 1; i <= re.n; i++)
		for (int j = 1; j <= re.m; j++)
			re.a[i][j] = 0;
	for (int k = 1; k <= x.m; k++)
		for (int i = 1; i <= re.n; i++)
			for (int j = 1; j <= re.m; j++)
				re.a[i][j] = jia(re.a[i][j], mul(x.a[i][k], y.a[k][j]));
	return re;
}

matrix jzksm(matrix x, int y) {
	matrix re = one;
	while (y) {
		if (y & 1) re = re * x;
		x = x * x;
		y >>= 1;
	} 
	return re;
}

int ksm(int x, int y) {
	int re = 1; x = mul(x, 1);
	while (y) {
		if (y & 1) re = mul(re, x);
		x = mul(x, x);
		y >>= 1;
	}
	return re;
}

int phi(int n) {//求 phi
	int re = n;
	for (int i = 2; i * i <= n; i++) {
		if (n % i == 0) {
			re = re / i * (i - 1);
			while (n % i == 0) n /= i;
		}
	}
	if (n > 1) re = re / n * (n - 1);
	return mul(re, 1); 
}

int work(int d) {
	A = jzksm(a, n / d - 1);
	int re = 0;
	for (int i = 1; i <= m; i++)//直接上 DP 的结果
		for (int j = 1; j <= m; j++)
			if (a.a[i][j])//记得首尾要连接,所以要首尾能在一起
				re = jia(re, A.a[i][j]);
	ans = jia(ans, mul(re, phi(d)));//直接乘上取这个值的可能性(phi)
}

int main() {
	for (int i = 1; i <= 10; i++) one.a[i][i] = 1;
	
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d %d", &n, &m, &k);
		
		one.n = one.m = m;
		a.n = a.m = m;
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= m; j++)
				a.a[i][j] = 1;
		for (int i = 1; i <= k; i++) {
			scanf("%d %d", &x, &y);
			a.a[x][y] = a.a[y][x] = 0;
		}
		
		ans = 0;
		for (int i = 1; i * i <= n; i++)
			if (n % i == 0) {//枚举 gcd(i,n) 的取值
				work(i); if (n / i != i) work(n / i);
			}
		
		ans = ans * ksm(n, mo - 2) % mo;
		printf("%d\n", ans);
	}
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值