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

原题链接.

刚了这么久就是没有往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 < = 550 n*m<=550 nm<=550,敏锐地意识到 m i n ( n , m ) < = 5 50 = 23 min(n,m)<=\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 < = m n<=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 < = m , O ( B ( n ) ∗ m ∗ m ∗ l o g   m ) n<=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
#include
#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);
}

你可能感兴趣的:(数论杂集)