• 【知识总结】Burnside 引理和 Polya 定理


    (原稿发送于 2019 年 7 月 10 日,大部分内容更新于 2020 年 5 月 5 日至 6 日)

    我第一次听说 Polya 原理是 NOIP2017 以前,但我觉得太难想着以后再学;

    NOIP2018 以前我觉得会考这玩意,下定决心学,后来咕了;

    WC2019 以前我觉得会考这玩意,下定决心学,后来咕了;

    SNOI2019 以前我觉得会考这玩意,下定决心学,后来咕了;

    CTS2019 以前我觉得会考这玩意,下定决心学,后来咕了;

    APIO2019 以前我觉得会考这玩意,下定决心学,后来咕了;

    THUSC2019 以前我觉得会考这玩意,下定决心学,后来咕了;

    今天距离 NOI2019 还有不到一周,我又下定决心学,赶紧抓着 jumpmelon 让他给我讲,然后他两句话就说完了。

    摔.jpg

    由于 Polya 原理是我临时抱瓜脚学的,所以这篇博客里只有不严谨的结论,严谨了表述和证明什么的 NOI 以后 看心情 再补。

    基于上述原因,这篇博客的吐槽环节可能比正文(不含代码)还长

    问题:有一个 (n) 个点的环和 (m) 种颜色,求给每一个点染一种颜色的本质不同的方案数。两种方案本质不同当且仅当它们不能通过旋转互相得到。

    结论:方案数是 (frac{1}{n}sum_{i=1}^n m^{gcd(n,i)})

    Updated: 2020 年 5 月 5 日

    这都快 NOI2020 了,我终于想起来更新了 qwq 。

    主要参考资料:充满对称性的计数——Burnside引理与Polya定理 - 知乎

    在无特殊说明的情况下,下文中所有序列的长度均为 (N)

    定义

    这些定义根据本文所需简化了,可能与严谨的定义有出入。

    置换:若序列 (A={a_1,a_2,dots,a_N}) 经过一次变换后变为 (A'={a_{p_1},a_{p_2},dots,a_{p_N}}) ,其中 (P={p_1,p_2,dots,p_N})([1,N]) 的一个排列,则这个操作称为置换操作,记作 (A*P=A') ,这个排列称为置换。

    不动点(P) 中满足 (i=P_i)(i) 的数量称为 (P) 的不动点数。

    置换群:若一个置换的集合 (S) 满足以下条件,那么这个置换的集合称为置换群:

    • 封闭性:对于 (P_1in S)(P_2in S) ,有(P_1*P_2in S)
    • 存在单位元:存在一个置换使得任意序列都是它的不动点。
    • 存在逆元:对于任意序列 (a) 能通过置换操作成为的 (a')(a') 也能通过置换操作成为 (a) (即存在逆元)。

    (置换本身满足结合律,所以上述置换群的定义似乎和常见的群的定义差不多)

    等价类:对于置换群 (S) ,若序列的集合 (E) 中的所有序列都可以通过若干次置换操作成为 (E) 中任意一个元素,则 (E) 称为一个等价类。根据置换群的定义,(E) 中的任意一个元素通过若干次置换操作后的结果一定在 (E) 中。等价类在 OI 题目中常常表述为「本质相同的方案」之类。

    一个我也不知道叫什么的定理

    好像叫什么「轨道 - 中心化子定理」

    (G) 为置换群,对于序列中任意一个元素 (k)(E_k) 是它所在的等价类(根据定义显然每个元素在且仅在一个等价类中),(Z_k) 是所有 (k) 是不动点的置换组成的集合。则:

    [|G|=|E_k| imes|Z_k| ]

    警告 :证明比较繁琐,可以选择不看。

    证明:如果 (|E_k|=1) ,那么显然对于 (G) 中的任意一个置换都有 (p_k=k) ,即 (k) 是所有置换的不动点,(|Z_k|=|G|) ,上式显然成立。

    否则,考虑 (E_k) 中一个不同于 (k) 的点 (u) ,任选一个 (P_k=u) 的置换 (P) (即 (k) 通过 (P) 置换到 (u) ),那么显然 (P otin Z_k) 。令 (Z_{k,u}={x|x=P*P',P'in Z_k}) 。根据置换群的封闭性, (Z_{k,u}subset G) 。此外 (Z_{k,u}) 还有一些性质:

    (|Z_{k,u}|=|Z_k|) 。根据置换的可逆性,若 (P_1 eq P_2) ,则 (P*P_1 eq P*P_2) ,因此 (P*P',P'in Z_k) 互不相等,所以 (|Z_{k,u}|=|Z_k|)

    任意一个 (P_k=u)(P) 构造出的 (Z_{k,u}) 都相同。若 (P_k=u)(P'_k=u) ,任取 (Qin Z_k) ,考虑 ((P'^{-1}*P*Q)_k) 。因为 (P_k=P'_k) ,所以在两边置换前先进行一次 (P') 的逆置换仍然是相等的,即 ((P'^{-1}*P)_k=(P'^{-1}*P')_k=k) 。那么 ((P'^{-1}*P*Q)_k=Q_k=k) ,即 (P'^{-1}*P*Qin Z_k)

    (Q'=P'^{-1}*P*Q) ,则任意一个 (P*Q) 都能对应到一个 (P'*Q',Q'in Z_k) ,即一个由 (P') 构造出的 (Z_{k,u}) 中的元素。由于 (P)(P') 的任意性,所以反之亦然。

    (Z_{k,u}cap Z_k=empty) 。假设置换 (Qin Z_{k,u}cap Z_k) ,因为 (Q=P*P') 相当于从 (k) 先变换到 (u) ,再从 (u) 变换到 (P'_u) (即 (Q_k) )。

    (Q_k=k) 说明 (P'_u=k) ,又因为 (P'in Z_k) 所以 (P'_k=k) ,这样 (P') 中就有两个 (k) ,与排列的定义矛盾。因此不存在这样的 (Q)

    (Z_{k,u}cap Z_{k,v}=empty, u,vin E_k)

    假设 (P_k=u,P'_k=v)(Q,Q'in Z_k) 。那么 (P*Qin Z_{k,u},P'*Q'in Z_{k,v})

    (P*Q=P'*Q') ,两边同时乘上 (Q^{-1}) (显然 (Q^{-1}_k=Q_k=k)(Q^{-1}in Z_k) )得到 (P=P'*Q'*Q^{-1}) ,那么 (P_k=(P'*Q'*Q^{-1})_k=P_k=(P'*(Q'*Q^{-1}))_k)

    由于 (Q'in Z_k,Q^{-1}in Z_k) ,那么 (Q'*Q^{-1} in Z_k) ,即通过 ((Q'*Q^{-1})) 置换对 (k) 没有影响。那么就有 (P'_k=P_k=u) ,与 (P'_k=v) 矛盾,故这种情况不存在。

    结论

    综上所述,对于所有 (u eq k,uin E_k)(Z_{k,u}) 的大小均为 (|Z_k|) 且互不相交。显然,所有不在 (Z_k) 中的置换 (P) 都属于且仅属于 (Z_{k,P_k}) ,再加上 (Z_k) 中的置换,就有 (|G|=|E_k| imes |Z_k|)

    バーンサイド Burnside 引理

    (C(P)) 表示置换 (P) 的不动点个数,则对于置换群 (G) ,等价类个数 (L)(G) 中各个置换的不动点数的平均数。即:

    [L=frac{1}{|G|}sum_{Pin G} C(P) ]

    证明:根据上面的定理,每个等价类中的点的 (|Z_k|) 都相等,因此每个等价类中 (|E_k|) 个点的 (|Z_k|) 之和就是 (|G|) 。那么,所有点的 (|Z_k|) 之和,也就是 (sum_{Pin G} C(P)) 之和,就是等价类数乘上 (|G|)

    一个常见的模型

    有一个长为 (n) 的圆环,每个位置可以填 ([1,K]) 中任意一个整数,求本质不同的填数方案数。通过旋转和翻转能够互相得到的方案视为本质相同。

    (严谨地定义一下:对于序列 ({a_0,a_1,dots,a_{n-1}}) ,旋转 (k) 位就是把 (a_i) 变成 (a_{i+kmod n}) ,翻转就是把 (a_i) 变成 (a_{-imod n}) 。)

    这里包含 (2n) 个置换:不翻转并旋转 (k) 位和翻转并旋转 (k)((0leq k<n)) 。这些置换显然构成了一个置换群。把每种染色方案看作上述序列中的一个点(共 (K^n) 个),则每种方案能通过这些置换得到的方案都是本质相同的方案。这样就把题目转化成了等价类计数问题,可以直接套用 Burnside 引理。

    但是,使用 Burnside 引理需要算出每个置换的不动点数,所以需要对每个置换分别枚举 (K^n) 种填数方案,并检查这些方案是否是不动点,时间复杂度太高了。对于这个问题,可以使用 Polya 定理来优化。

    ポリア Polya 定理

    我并不是特别确定这个定理指的是哪个具体的公式。我更愿意把它看作一种求不动点数的一般思路。

    对于一个置换 (P) ,把序列的每个位置看成一个点,从 (i)(P_i) 连边,这样一定形成的是若干个有向环。在上述问题中,如果某种方案对于 (P) 是不动点,那么它一定满足每个环上的点填的数都相同(显然这样才能保证置换后不变),而环与环之间则互不影响。因此,置换 (P) 的不动点数就是 (K^m) ,其中 (m) 是环的数量。

    特殊地,考虑上述那个模型。对于不翻转的置换,如果旋转 (k) 位,那么环的数量就是 (gcd(n,k)) (所有点按照模 (gcd(n,k)) 的余数分类。证明略)。而翻转后再旋转 (k) 位的置换的环的数量则比较复杂。

    根据上面对「翻转」的定义,第 (i) 位的数翻转后再旋转 (k) 位后会到第 (-i+kmod n) 位。因为 (-(-i+k)+k=i) ,所以这个置换最多做两次就会回到本身,也就是环长最多是 (2) 。那么,环的数量就是 (frac{n-a}{2}+a) ,其中 (a) 是自环的数量,也就是满足 (-i+k=imod n)(i) 的数量。接下来考虑 (a) ,也就是如下方程的解的个数怎么求。

    [2i=kmod n ]

    (n) 是奇数时存在 (2) 的逆元,因此有且只有一个解。环的数量是 (frac{n-1}{2}+1)

    (n) 是偶数时,若 (k) 是偶数则存在 (frac{k}{2})(frac{k+n}{2}) 两个解,若 (k) 是奇数则无解。环的数量是 (frac{n-2}{2}+2=frac{n}{2}+1)(k) 是偶数)或 (frac{n}{2})(k) 是奇数)。

    例题:洛谷 4980 Polya 定理

    相当于只考虑不翻转的 (n) 个置换的情况。直接套用上面的结论,答案是:

    [egin{aligned} ans&=frac{1}{n}sum_{i=1}^n n^{gcd(n,i)}\ &=frac{1}{n}sum_{d|n}n^dsum_{i=1}^{frac{n}{d}}[gcd(frac{n}{d},i)=1]\ &=frac{1}{n}sum_{d|n}n^dvarphi(frac{n}{d}) end{aligned}]

    (n) 分解质因子然后按照质因子搜索它的所有因数,搜索时维护一下 (varphi) 就好了

    代码:

    #include <cstdio>
    #include <cstring>
    #include <cctype>
    #include <algorithm>
    using namespace std;
    
    namespace zyt
    {
    	template<typename T>
    	inline bool read(T &x)
    	{
    		char c;
    		bool f = false;
    		x = 0;
    		do
    			c = getchar();
    		while (c != EOF && c != '-' && !isdigit(c));
    		if (c == EOF)
    			return false;
    		if (c == '-')
    			f = true, c = getchar();
    		do
    			x = x * 10 + c - '0', c = getchar();
    		while (isdigit(c));
    		if (f)
    			x = -x;
    		return true;
    	}
    	template<typename T>
    	inline void write(T x)
    	{
    		static char buf[20];
    		char *pos = buf;
    		if (x < 0)
    			putchar('-'), x = -x;
    		do
    			*pos++ = x % 10 + '0';
    		while (x /= 10);
    		while (pos > buf)
    			putchar(*--pos);
    	}
    	typedef long long ll;
    	typedef pair<int, int> pii;
    	const int SQRTN = 4e4 + 10, P = 1e9 + 7;
    	pii prime[SQRTN];
    	int pcnt, n, ans;
    	int power(int a, int b)
    	{
    		int ans = 1;
    		while (b)
    		{
    			if (b & 1)
    				ans = (ll)ans * a % P;
    			a = (ll)a * a % P;
    			b >>= 1;
    		}
    		return ans;
    	}
    	int inv(const int a)
    	{
    		return power(a, P - 2);
    	}
    	void get_prime(int n)
    	{
    		pcnt = 0;
    		for (int i = 2; i * i <= n; i++)
    			if (n % i == 0)
    			{
    				prime[pcnt++] = pii(i, 0);
    				while (n % i == 0)
    					++prime[pcnt - 1].second, n /= i;
    			}
    		if (n > 1)
    			prime[pcnt++] = pii(n, 1);
    	}
    	void dfs(const int pos, const int mul, const int pmul, const int div)
    	{
    		if (pos == pcnt)
    		{
    			ans = (ans + (ll)mul / div * pmul * power(n, n / mul)) % P;
    			return;
    		}
    		dfs(pos + 1, mul, pmul, div);
    		for (int i = 1, tmp = prime[pos].first; i <= prime[pos].second; i++, tmp *= prime[pos].first)
    			dfs(pos + 1, mul * tmp, pmul * (prime[pos].first - 1), div * (prime[pos].first));
    	}
    	int work()
    	{
    		int T;
    		read(T);
    		while (T--)
    		{
    			ans = 0;
    			read(n);
    			get_prime(n);
    			dfs(0, 1, 1, 1);
    			write((ll)ans * inv(n) % P), putchar('
    ');;
    		}
    		return 0;
    	}
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
    	freopen("4980.in", "r", stdin);
    #endif
    	return zyt::work();
    }
    
  • 相关阅读:
    VS2013快速安装教程
    软件工程课程的感想
    GitHub和Microsoft TFS对比有什么优势
    拼接素数
    C语言程序题
    vue中的实例方法的底层原理
    ios 安卓
    防抖
    伪数组转真数组的放法
    http和https的一种能力?
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/12840127.html
Copyright © 2020-2023  润新知