浅析群论
Group Theory - 浅析群论
- Group Theory - 浅析群论
- 更好的阅读体验戳此进入
- 群
- 其它群
- 双半群模型
- 阶
- 子群
- 陪集
- 拉格朗日定理
- 置换
- 置换群
- 群作用
- 轨道-稳定子定理
- Burnside定理
- 例题 #1 LG-P4980 【模板】Pólya 定理
- Polya定理
- 例题 #2 LG-P4727 [HNOI2009]图的同构计数
- 例题 #3 LG-P4128 [SHOI2006] 有色图
- 例题 #4 UVA10601 Cubes
- 例题 #5 LG-P2561 [AHOI2002]黑白瓷砖
- 例题 #6 SP419 TRANSP - Transposing is Fun
- 例题 #7 SP422 TRANSP2 - Transposing is Even More Fun
- 例题 #8 LG-P4916 [MtOI2018]魔力环
- UPD
更好的阅读体验戳此进入
Tips:不保证正确性,不保证没锅,部分定理可能只是口糊的证明。
群
称集合
- 封闭性(closure)
若
,则 。
- 结合律(associativity)
若
,则 。
- 单位元(identity element,亦称幺元)
满足 ,则 称为单位元,其有且仅有一个。
考虑证明,有两个不同的单位元
然后根据这个还有个性质:若有
大概的证明:
- 逆元(inverse element)
,总 ,使得 ,则 为 的逆元,有且仅有一个。
考虑证明,首先需要另一个性质,即:$ (a{-1}) = a
证明:
则有:
然后考虑证明逆元唯一,如果逆元不唯一,我们可以把刚才的性质换一种叙述,任意元素的任意逆元的任意逆元均为元素本身。首先我们可以把群中每个元素作为一个点,逆元的关系当作边,抽象成一个图,由刚才的性质显然应为无向边。如果一个点有多个逆元,那么其应有多条边,且连接的所有其它点度数应仅为
e.g. 如整数集和整数间加法构成群
在实际应用上可以认为树状数组的元素和运算符必须满足群,如
其它群
半群
称
幺半群(亚群、含幺子群、独异点)
称
交换半群
称
交换幺半群(交换亚群)
称
双半群模型
首先明确一下,该模型一般适用于对于修改与查询的范围有限制的问题中,如:序列区间,树上简单路径,二维平面矩形,高位正交范围,半平面范围,圆范围。
这里的
表示 的幂集,即 的所有子集构成的集合。
具体来说,给定初始集合
满足结合律,且满足
存在初始权值
按序执行
- 范围修改,给定
,定义若 ,则 ,反之 。 - 范围查询,给定
,定义 ,对所有 ,答案则为 。
所有操作与初始权值均离线给出,用
对于满足上述大量限制的模型,我们称为双半群模型。
而为了能够通过线段树等数据结构维护,一般维护的信息都一定需满足半群性质,且大多为交换半群的性质。
而对于一道具体的题目,我们的目的就是去设计
再具体的我也就不会了,总之这似乎是一个很高妙的东西,已知存在的一个可以用的题 LG-P8868 [NOIP2022] 比赛。
阶
定义群的阶为群中元素的数量,记作
子群
若
陪集
定义与性质
考虑存在
我们称
同理对于
对于右陪集,满足以下
。
证明:对于
,如果 ,那么 $ h_1 = h_2 \texttt{QED} $。
。
证明:单位元唯一,子集也成群,则子集一定包含该单位元,则有 $ e \circ g \in \mathbb{H}g \Longleftrightarrow g \in \mathbb{H}g
\texttt{QED} $。
。
证明:充分性由封闭性易证。必要性:考虑单位元
,由题意显然有 $ e \circ g = g \in \mathbb{H} \texttt{QED} $。
。
证明:充分性:$ \mathbb{H}a = \mathbb{H}b \Longleftrightarrow \mathbb{H} a \circ b^{-1} = \mathbb{H} b \circ b^{-1} \Longleftrightarrow \mathbb{H} a \circ b^{-1} = \mathbb{H}
a \circ b^{-1} \in \mathbb{H} \texttt{QED} $。必要性反之亦然。
。
换句话说,一个子群的陪集的交集要么为空,要么相等。
证明:假设有
,且 ,则 ,同 一个 则有 ,显然 ,则 ,由性质4可得 $ \mathbb{H}a = \mathbb{H}b \texttt{QED} $。
的所有右陪集的并为 。
证明:显然
,则对于每个关于 的右陪集都含有 ,所有右陪集则包含了 中的每一个元素,即为 $ G \texttt{QED} $。
常见表述
若满足
若满足
拉格朗日定理
对于有限群
还有一种表述
证明很显然,考虑性质1,5,6易证。
置换
定义
一般用双行表示法表示一个置换,即如:
比较易于理解,即用第二个元素替换第一个元素,第四个替换第二个,以此类推。
特别地,不失一般性,若前者排列为
所以不难发现,我们可以认为一个序列,或者更严谨的说,一个排列,既可以是一个序列,也可以是一个置换。
同时可以证明,不论表示为双行亦或单行,长度为
同时需要注意一点,对于抽象代数中,或者更具体地说,对于群论中,我们认为置换即为集合(通常为有限集)对自身的双射,所以置换应该认为是严格的排列,而组合数学中只要求无重复,但却是可以有阙漏的。但当然本文中的置换均指严格的双射。
运算
对于置换
当然更严谨地说,实际上应该是对于置换
具体来说,个人理解该合成运算是可以定义在置换和序列之间的,也就是对于如
置换群
定义
称对于集合
简而言之,置换群即为若干置换和置换之间的合成运算构成的群。
显然置换群存在单位元 $ e = (1, 2, \cdots, n)
同时显然
群作用
群对自身的作用
对于群
如此对于群中每个元素都能形成一个这样的映射,此时我们可以考虑将所有的映射放到一起,定义为一个二元的映射,即
然后这个东西一般我们称其为左平移作用,同理也存在右平移作用,这里不再赘述。
群作用
更一般地,对于对于群
结合律:对于
单位元恒等映射:对于
则称群
不难想到该映射应为双射,考虑证明:对于
但是注意,不同的
轨道-稳定子定理
轨道
对于作用在集合
稳定子
记
简而言之就是群中所有满足
轨道-稳定子定理
抽象一点地表述,轨道-稳定子定理即为:
即元素
证明
首先
满足结合律,封闭性,以及存在逆元易证,这里不再赘述。
我们可以先考虑一个固定的
显然可能存在
此处我们记
换句话说,满足
显然
此时我们将刚才的结论换种表述方式,即对于 $ g_1(x) = x_1
同理此结论推导至
现在我们再继续推导,仍令
现在我们再把目光放回证明的前半部分,只有稳定子能够使得
Tips:这里我们可以在回过头考虑为什么每一段
证明2
除了上面的方法之外,仍存在一个思路:
根据拉格朗日定理可知
于是问题转化为需要证明
也就是说稳定子的关于原群的不同陪集数量等于轨道的大小。
可以再次转化为证明每一个
然后这个地方的证明较为复杂,就不再赘述了。
Burnside定理
不动点
类似于稳定子,对于稳定子中 $ g(x) = x
对于集合
等价类
对于置换群
定理
同等价类的定义中,不同的等价类的数量,记作
也就是说等价类的数量等于每一个
证明
首先考虑把不动点转化为稳定子,较为显然:
然后用轨道-稳定子定理转化一下,即:
然后把分子提出来,再将枚举
显然一个等价类的轨道的大小就是等价类的大小,所以此时
不难发现有:
所以最终转化为:
也就是等价类的个数了,所以最终式子化为:
然后把这个带入原定理的
Tips
对于这个东西有一个例子,十分形象:
例题 #1 LG-P4980 【模板】Pólya 定理
题面
求
Solution
原题的问题可以转化为:
在置换群
根据 Burnside定理 可知答案即为:
然后考虑一下这个枚举的过程。
显然对于旋转
对于旋转
故最终答案可以转化为:
然后不难发现这东西就是个标准莫反,考虑枚举
然后右边的显然是
所以最终式子为:
然后大概分析一下这个的复杂度,枚举因数是
然后似乎直接强行枚举
当然,因为本题题目名就是 Polya定理 模板,那么我们可以再考虑一下 Polya 的推导方式。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (ll)(1e9 + 7)
template < typename T = int >
inline T read(void);
bitset < 110000 > notPrime;
basic_string < ll > Prime;
void Init(void){
for(ll i = 2; i <= 101000; ++i){
if(!notPrime[i])Prime += i;
for(auto p : Prime){
if(p * i > 101000)break;
notPrime[p * i] = true;
if(i % p == 0)break;
}
}
}
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
ll phi(ll x){
ll ret(x);
basic_string < ll > fact;
for(auto p : Prime){
if(p * p > x)break;
if(x % p == 0)fact += p;
while(x % p == 0)x /= p;
}if(x != 1)fact += x;
for(auto p : fact)ret /= p, ret *= (p - 1);
return ret;
}
int main(){
Init();
int T = read();
while(T--){
ll N = read < ll >();
ll ans(0);
for(ll i = 1; i * i <= N; ++i){
if(N % i != 0)continue;
(ans += qpow(N, i) * phi(N / i) % MOD) %= MOD;
if(i * i != N)(ans += qpow(N, N / i) * phi(i) % MOD) %= MOD;
}(ans *= qpow(N, MOD - 2)) %= MOD;
printf("%lld\n", ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
Polya定理
这里我们基于刚才的模板题来叙述 Polya定理。
这个东西我认为也可以看作是 Burnside定理 的推论。
考虑一个置换,用
令环的数量为
也就是每个环任意选择一个颜色。
然后推导是类似的,对于旋转
例题 #2 LG-P4727 [HNOI2009]图的同构计数
题面
一道 NP 问题,求互不同构的含
Solution
首先称两个图同构,当且仅当两个图可以通过对顶点的重标号使得两图完全相同。
然后这个东西的答案是一个数列,OEIS 上的编号是 A000088。
然后我们再次回到 Burnside定理,集合
然后我们的难点依然在于求不动点的数量。也就是说对于一个置换
下面将会有一个与上一题,即 Polya定理 模板,或者说群论的标准套路。我们可以考虑先将图认为是完全图,然后用两种颜色对边进行染色,两种颜色表示该边存在或不存在。于是此时我们就会发现对于某一个置换
举个例子,对于一个标号为
所以说上面我们说的这一对,就是标准的 Polya定理,即
所以现在关键就在于我们如何求这个
首先我们会发现,对于一个置换
考虑若将置换
首先考虑对于两个点均在同一段循环内的边:
然后我们考虑对于一种置换中的一个长度为
我们可以将对应的点数排成一个正
不难发现对于长度相同的边都是等价的,其必须同时染或不染,否则置换后将会不同构。而不难想到,由于正
于是其贡献的等价类数量为:
然后考虑在不同循环中的边:
举个例子:
这个东西的图大概是这样的:
如对于长度为
所以这些边的贡献的等价类个数即为:
所以最终等价类的个数即为:
答案即为:
不过
考虑发现很多置换会被重复计算,如:
这样的
也就是说对于相同的
考虑对于一种拆分,计算有多少种对应的置换,首先总共有
然后这里再枚举每个
然后化简一下即为:
这样便可以通过了。然后尝试分析一下复杂度:
枚举
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (997)
template < typename T = int >
inline T read(void);
int N;
ll fact[110], inv[110], inv_d[110];
int cnt[110];
ll ans(0);
basic_string < int > cur;
unordered_set < int > exist;
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(void){
for(int i = 1; i <= 100; ++i)inv_d[i] = qpow(i, MOD - 2);
fact[0] = 1;
for(int i = 1; i <= 100; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[100] = qpow(fact[100], MOD - 2);
for(int i = 99; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
void dfs(int lft = N){
if(!lft){
ll C(0);
for(auto i : cur)C += i >> 1;
for(int i = 1; i <= (int)cur.size(); ++i)
for(int j = 1; j <= i - 1; ++j)
C += __gcd(cur.at(i - 1), cur.at(j - 1));
ll ret = qpow(2, C);
for(auto i : cur)(ret *= inv_d[i]) %= MOD;
for(auto i : exist)(ret *= inv[cnt[i]]) %= MOD;
(ans += ret) %= MOD;
return;
}
for(int i = cur.empty() ? 1 : cur.back(); i <= lft; ++i){
cur += i, ++cnt[i], exist.insert(i);
dfs(lft - i);
cur.pop_back();
if(!--cnt[i])exist.erase(i);
}
}
int main(){
Init();
N = read();
dfs();
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #3 LG-P4128 [SHOI2006] 有色图
双倍经验,将
例题 #4 UVA10601 Cubes
题面
给定
Solution
依然尝试考虑 Burnside定理,即:
集合
结论:对于正方体的旋转共有
对于恒等变换,共
对于沿对面中心定向旋转
对于沿对面中心旋转
对于沿对棱中点旋转
对于沿相对顶点定向旋转
故置换的个数共
如果直接枚举所有排列然后计算不动点再去重似乎有点卡,感觉复杂度似乎是
考虑将构建长方体转换为进行圆排列,不难发现大多数的置换的环长都是相同的,令环长为
对于不满足
然后对于两种环的部分,枚举两个环长为
最后复杂度总之是
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
int cnt[7];
ll fact[13];
void Init(void){
fact[0] = 1;
for(int i = 1; i <= 12; ++i)fact[i] = fact[i - 1] * i;
}
ll Cal(int len, int siz = 12){
if(siz % len)return 0;
ll ret = fact[siz / len];
for(int i = 1; i <= 6; ++i){
if(cnt[i] % len)return 0;
else ret /= fact[cnt[i] / len];
}return ret;
}
int main(){
Init();
int T = read();
while(T--){
memset(cnt, 0, sizeof cnt);
for(int i = 1; i <= 12; ++i)cnt[read()]++;
ll ans(0);
ans += Cal(1) + 6 * Cal(4) + 3 * Cal(2) + 8 * Cal(3);
for(int i = 1; i <= 6; ++i){
if(!cnt[i])continue;
--cnt[i];
for(int j = 1; j <= 6; ++j){
if(!cnt[j])continue;
--cnt[j];
ans += Cal(2, 10) * 6;
++cnt[j];
}++cnt[i];
}printf("%lld\n", ans / 24);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #5 LG-P2561 [AHOI2002]黑白瓷砖
题面
有点繁琐,挂两个图片吧。
Solution
显然置换群为:
分别考虑,对于旋转,考虑 Polya定理,每个环长均为
对于翻转,要求对称,环长为
对于静止,贡献为:
套用一下 Polya定理 即可。
注意需要高精度。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
int N;
class Bignum{
private:
public:
basic_string < int > nums;
friend Bignum operator + (Bignum a, Bignum b){
reverse(a.nums.begin(), a.nums.end());
reverse(b.nums.begin(), b.nums.end());
while(a.nums.size() < b.nums.size())a.nums += 0;
while(b.nums.size() < a.nums.size())b.nums += 0;
Bignum ret; bool plus(false);
for(int i = 0; i < (int)a.nums.size(); ++i){
a.nums.at(i) += b.nums.at(i) + plus;
plus = false;
if(a.nums.at(i) >= 10)
plus = true, a.nums.at(i) %= 10;
}
if(plus)a.nums += 1;
reverse(a.nums.begin(), a.nums.end());
return a;
}
friend Bignum operator * (Bignum a, Bignum b){
reverse(a.nums.begin(), a.nums.end());
reverse(b.nums.begin(), b.nums.end());
Bignum ret;
for(int i = 1; i <= (int)(a.nums.size() + b.nums.size()); ++i)ret.nums += 0;
for(auto i = 0; i < (int)a.nums.size(); ++i)
for(int j = 0; j < (int)b.nums.size(); ++j)
ret.nums.at(i + j) += a.nums.at(i) * b.nums.at(j);
for(int i = 0; i < (int)ret.nums.size() - 1; ++i)
ret.nums.at(i + 1) += ret.nums.at(i) / 10, ret.nums.at(i) %= 10;
if(ret.nums.back() >= 10)ret.nums += ret.nums.back() / 10, *prev(ret.nums.end(), 2) %= 10;
while(ret.nums.size() > 1 && ret.nums.back() == 0)ret.nums.pop_back();
reverse(ret.nums.begin(), ret.nums.end());
return ret;
}
friend Bignum operator / (Bignum a, ll div){
Bignum ret;
ll cur(0); bool flag(false);
for(auto i : a.nums){
cur *= 10, cur += i;
if(cur < div && !flag)continue;
flag = true, ret.nums += cur / div, cur %= div;
}return ret;
}
void Print(void){
for(auto v : nums)printf("%d", v);
printf("\n");
}
};
Bignum qpow(Bignum a, ll b){
Bignum ret, mul(a);
ret.nums += 1;
while(b){
if(b & 1)ret = ret * mul;
b >>= 1;
mul = mul * mul;
}return ret;
}
int main(){
N = read();
Bignum ans; ans.nums += 0;
Bignum base; base.nums += 2;
ans = ans + (qpow(base, (ll)ceil((ld)N * (N + 1) / 6.0)) * base);
ll num(0);
for(int i = 1; i <= N; ++i)num += (i + 1) >> 1;
Bignum mul; mul.nums += 3;
ans = ans + (qpow(base, num) * mul);
ans = ans + qpow(base, (N * (N + 1)) >> 1);
ans = ans / 6ll;
ans.Print();
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #6 SP419 TRANSP - Transposing is Fun
题面
给定
Solution
一道很好的群论题,尤其是其中转化的思想还是很高妙的。
不难发现,我们考虑按序为矩阵标号,从
这个东西朴素的交换次数是
由题面中
于是我们想到,若将二进制数抽象为长度为
这时就存在一个很人类智慧的转化,我们可以容易想到每
用经典的莫反思想推导:
可以通过精细实现达到
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (1000003ll)
#define LIMIT (510000)
template < typename T = int >
inline T read(void);
int A, B;
int N;
basic_string < int > Prime;
bitset < LIMIT + 100 > notPrime;
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
int Phi(int x){
int ret(x);
basic_string < int > fact;
for(auto p : Prime){
if(p * p > x)break;
if(x % p == 0)fact += p;
while(x % p == 0)x /= p;
}if(x != 1)fact += x;
for(auto f : fact)ret /= f, ret *= (f - 1);
return ret;
}
int main(){
for(int i = 2; i <= LIMIT; ++i){
if(!notPrime[i])Prime += i;
for(auto p : Prime){
if(p * i > LIMIT)break;
notPrime[p * i] = true;
if(i % p == 0)break;
}
}
int T = read();
while(T--){
A = read(), B = read();
N = (A + B) / __gcd(A, B);
ll ans(0);
for(int i = 1; i * i <= N; ++i){
if(N % i)continue;
if(i * i != N)
(ans += qpow(2, (ll)__gcd(A, B) * i) * Phi(N / i) % MOD) %= MOD,
(ans += qpow(2, (ll)__gcd(A, B) * (N / i)) * Phi(i) % MOD);
else (ans += qpow(2, (ll)__gcd(A, B) * i) * Phi(N / i) % MOD) %= MOD;
}(ans *= qpow(N, MOD - 2)) %= MOD;
printf("%lld\n", (qpow(2, A + B) - ans + MOD) % MOD);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #7 SP422 TRANSP2 - Transposing is Even More Fun
题面
与 SP419 TRANSP - Transposing is Fun 相同,仅略增大数据范围。
Solution
有点不太理解这道题的意义,完全就是更卡常一点,需要更加精细实现,本质没有任何变化。。
推导部分完全相同,最终式子:
发现对于 dfs
跑一下,同时处理
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (1000003ll)
#define LIMIT (1100000)
template < typename T = int >
inline T read(void);
int A, B;
int N, gcdv;
ll ans(0);
ll pow2[LIMIT + 100];
basic_string < int > Prime;
bitset < LIMIT + 100 > notPrime;
struct Num{int v; int cnt;};
basic_string < Num > fact;
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
}
void Init(int x){
fact.clear();
for(auto p : Prime){
if(p * p > x)break;
if(x % p == 0)fact += {p, 0};
while(x % p == 0)fact.back().cnt++, x /= p;
}if(x != 1)fact += {x, 1};
}
void dfs(int dep = 0, ll d = 1, ll base = 1, ll div = 1){
if(dep == (int)fact.size())
return (ans += pow2[gcdv * (N / d)] * (d / div * base) % MOD) %= MOD, void();
dfs(dep + 1, d, base, div);
base *= (fact.at(dep).v - 1), div *= fact.at(dep).v;
for(int i = 1; i <= fact.at(dep).cnt; ++i)
d *= fact.at(dep).v, dfs(dep + 1, d, base, div);
}
int main(){
for(int i = 2; i <= LIMIT; ++i){
if(!notPrime[i])Prime += i;
for(auto p : Prime){
if(p * i > LIMIT)break;
notPrime[p * i] = true;
if(i % p == 0)break;
}
}
pow2[0] = 1;
for(int i = 1; i <= LIMIT; ++i)pow2[i] = pow2[i - 1] * 2 % MOD;
int T = read();
while(T--){
A = read(), B = read();
N = (A + B) / __gcd(A, B), gcdv = __gcd(A, B);
Init(N);
ans = 0;
dfs();
(ans *= qpow(N, MOD - 2)) %= MOD;
printf("%lld\n", (pow2[A + B] - ans + MOD) % MOD);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #8 LG-P4916 [MtOI2018]魔力环
题面
你需要对长度为
Solution
纯组合意义是不可能的,这辈子都不可能的。
发现本质不同关键字,不难想到群论,对于本题可以使用 Burnside,考虑令
考虑经典转化,即
转化为:
考虑处理
即经典插板法。
则对于
同时注意对于
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD (998244353ll)
#define LIM (210000)
template < typename T = int >
inline T read(void);
int N, M, K;
ll phi[LIM + 100];
bitset < LIM + 100 > notPrime;
basic_string < int > Prime;
ll fact[LIM + 100], inv[LIM + 100];
int main(){
auto qpow = [](ll a, ll b)->ll{
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}return ret;
};
auto Init = [qpow](void)->void{
phi[1] = fact[0] = 1;
for(int i = 2; i <= LIM; ++i){
if(!notPrime[i])phi[i] = i - 1, Prime += i;
for(auto p : Prime){
if((ll)i * p > LIM)break;
notPrime[i * p] = true;
if(i % p == 0)phi[i * p] = p * phi[i] % MOD;
else phi[i * p] = phi[p] * phi[i] % MOD;
if(i % p == 0)break;
}
}
for(int i = 1; i <= LIM; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[LIM] = qpow(fact[LIM], MOD - 2);
for(int i = LIM - 1; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
};Init();
auto GetC = [](ll n, ll m)->ll{
if(n < m || n < 0 || m < 0)return 0;
return fact[n] * inv[m] % MOD * inv[n - m] % MOD;
};
auto Cal = [qpow, GetC](ll N, ll M, ll K)->ll{
ll ret(0);
for(int i = 0, flag = 1; i * (K + 1) <= M; ++i, flag *= -1)
(ret += ((flag * GetC(N, i) * GetC(M - i * (K + 1) + N - 1, N - 1) % MOD) + MOD) % MOD) %= MOD;
(ret *= (N + M) * qpow(N, MOD - 2) % MOD) %= MOD;
return ret;
};
ll ans(0);
N = read(), M = read(), K = read();
if(N == M)printf("%d\n", K >= N ? 1 : 0), exit(0);
for(int i = 1; i <= N; ++i)
if(N % i == 0 && M % i == 0)
(ans += Cal((N - M) / i, M / i, K) * phi[i] % MOD) %= MOD;
(ans *= qpow(N, MOD - 2)) %= MOD;
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
后面就是烷烃计数烯烃计数等一大堆阴间东西,省选之后再来写~
UPD
update-2023_01_06 初稿
update-2023_01_09 fix大量笔误及不严谨的地方 补充了部分证明
update-2023_01_10 重写轨道-稳定子定理的证明
update-2023_02_04 调整部分排版
update-2023_02_06 添加例题 #5 #6 #7
update-2023_02_16 添加半群等内容 添加双半群模型
update-2023_03_06 添加例题 #8
本文作者:Tsawke
本文链接:https://www.cnblogs.com/tsawke/p/17032879.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步