SnackDown 2019 - Online Elimination Round Adi and the Matrix(二维Polya计数)

本文详细介绍了如何运用Burnside引理和Polya定理解决一个特定的矩阵计数问题,即求解不同n*m的01矩阵数量,通过置换群理论和整数划分技巧,提出了高效的算法解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接.

刚了这么久就是没有往burnside靠。

题目大意:

求有多少个不同的n*m的01矩阵。

两个矩阵相同,定义为:通过每次交换两行或两列若干次之后完全相同。

n*m<=550,答案模1e9+7

题解:

b u r n s i d e burnside burnside引理告诉我们:
不 同 数 = ∑ 每 种 置 换 不 动 点 个 数 置 换 数 不同数={\sum_{每种置换}不动点个数\over 置换数} =

显然对于一维的置换就是一个排列,二维的置换就是两个排列结合起来。

因此置换数为 n ! ∗ m ! n!*m! n!m!.

算这种东西肯定用 p o l y a polya polya定理方便:
如果只是一维的情况,那么 A n s = 2 环 的 个 数 Ans=2^{环的个数} Ans=2
考虑二维,对于一个长度为x的环和一个长度为y的环,显然形成的二维环的长度是 l c m ( x , y ) lcm(x,y) lcm(x,y),那么环的个数就是 x y / l c m ( x , y ) = g c d ( x , y ) xy/lcm(x,y)=gcd(x,y) xy/lcm(x,y)=gcd(x,y)

那么对于确定的置换, A n s = ∏ 2 g c d ( a [ i ] , b [ j ] ) Ans=\prod2^{gcd(a[i],b[j])} Ans=2gcd(a[i],b[j]),a和b为各维的各个环的长度。

暴力肯定是不行的,考虑优化这个东西。

n ∗ m &lt; = 550 n*m&lt;=550 nm<=550,敏锐地意识到 m i n ( n , m ) &lt; = 5 50 = 23 min(n,m)&lt;=\sqrt 550=23 min(n,m)<=5 50=23

但是显然 23 ! 23! 23!的也挂了,我们当然不用枚举具体的置换,我们只需要枚举一个整数划分,代表了各个环的长度,然后去算有多少个对应的排列。

假设这个整数划分是 a a a,首先是广义组合数来算,即 n ! ∏ a [ i ] ! {n!\over \prod a[i]!} a[i]!n!,,由于长度为 k k k的环的排列数为 ( k − 1 ) ! (k-1)! (k1)!,再乘上去,变成了 n ∏ a [ i ] n\over\prod a[i] a[i]n,其实长度相同的环的会重复算,设 b [ j ] b[j] b[j]表示长度为i的环的个数,还要除以b[j]!,最后结果为:
s u m = n ∏ a [ i ] ∏ b [ j ] sum={n\over \prod a[i]\prod b[j]} sum=a[i]b[j]n

那么使 n &lt; = m n&lt;=m n<=m,对 n n n进行整数拆分,对于一种整数拆分,可以处理一个 c n t [ x ] = ∏ i = 1 ∣ a ∣ 2 g c d ( x , a [ i ] ) cnt[x]=\prod_{i=1}^{|a|}2^{gcd(x,a[i])} cnt[x]=i=1a2gcd(x,a[i]),然后对 m m m进行整数拆分的dp,若分成一个 x x x,则乘上一个 c n t [ x ] cnt[x] cnt[x],还有就是之前的计算对应排列的方案数,这个是一样的。

复杂度为:
n &lt; = m , O ( B ( n ) ∗ m ∗ m ∗ l o g   m ) n&lt;=m,O(B(n)*m*m*log~m) n<=m,O(B(n)mmlog m),其中的log是由 ∑ i = 1 m m / i ≈ m l o g m \sum_{i=1}^mm/i≈mlogm i=1mm/imlogm来的。

B ( n ) B(n) B(n)表示n的整数拆分数。

当n=23时,整数拆分为1000多种

如果n大了,虽然B(n)大了,m就变小了,因此是跑得过的。

感谢Samjia的指导。

Code:

#include<cstdio>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
using namespace std;

const int mo = 1e9 + 7;

const int N = 555;

int n, m;

ll ksm(ll x, ll y) {
	ll s = 1;
	for(; y; y /= 2, x = x * x % mo)
		if(y & 1) s = s * x % mo;
	return s;
}

ll ni[N], fac[N], a2[N], nf[N];

int a[N];

ll cnt[N];

int gcd(int x, int y) {
	return !y ? x : gcd(y, x % y);
}

ll ans, f[N];

void dg(int x, int y) {
	if(x == 0) {
		ll s = fac[n];
		fo(i, 1, a[0]) s = s * ni[a[i]] % mo;
		int la = 1;     
		fo(i, 2, a[0] + 1) if(i > a[0] || a[i] != a[i - 1]) {
			s = s * nf[i - la] % mo;
			la = i;
		}
		fo(i, 1, m) {
			cnt[i] = 1;
			fo(j, 1, a[0]) cnt[i] *= a2[gcd(i, a[j])], cnt[i] %= mo;
		}
		fo(i, 0, m) f[i] = 0;
		f[0] = fac[m];
		fo(i, 1, m) {
			fd(j, m, i) {
				ll mi = 1;
				for(int k = 1; i * k <= j; k ++) {
					mi = mi * ni[i] % mo * cnt[i] % mo;
					f[j] = (f[j] + f[j - i * k] * mi % mo * nf[k]) % mo;
				}
			}
		}
		ans = (ans + f[m] * s) % mo;
		return;
	}
	fo(i, y, x) {
		a[++ a[0]] = i;
		dg(x - i, i);
		a[0] --;
	}
}

int main() {
	scanf("%d %d", &n, &m);
	if(n > m) swap(n, m);
	fac[0] = 1; fo(i, 1, m) fac[i] = fac[i - 1] * i % mo;
	nf[m] = ksm(fac[m], mo - 2);
	fd(i, m, 1) nf[i - 1] = nf[i] * i % mo;
	fo(i, 1, m) ni[i] = ksm(i, mo - 2);
	a2[0] = 1; fo(i, 1, m) a2[i] = a2[i - 1] * 2 % mo;
	dg(n, 1);
	printf("%lld", ans * nf[n] % mo * nf[m] % mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值