置换环与并查集,置换环进阶与计数,区间与图
C++ 置换环与并查集、置换环进阶与计数、区间与图 完全详解
本文系统讲解:置换环与并查集、置换环进阶与计数、区间与图。
代码统一使用数组模拟,不使用动态指针,不使用链式前向星(用vector存图)。
代码中运算符左右空格已删除。
含公式、定理、推论、专业名词解释、ASCII 图示、典型例题与完整代码。
目录
- 置换环与并查集
1.1 置换的定义
1.2 置换的循环分解
1.3 置换环
1.4 用并查集找置换环
1.5 置换环的性质与定理
1.6 典型例题:P1330 封锁阳光大学(并查集)
1.7 典型例题:CF 500B New Year Permutation
1.8 典型例题:洛谷 P1525 关押罪犯(扩展域并查集)
1.9 典型例题:洛谷 P2024 食物链(带权并查集) - 置换环进阶与计数
2.1 置换的乘法
2.2 置换的逆
2.3 置换的阶
2.4 循环长度的 LCM
2.5 置换计数
2.6 Burnside 引理
2.7 Polya 定理
2.8 置换开方
2.9 置换环上的 DP
2.10 典型例题:洛谷 P4980 Polya 定理
2.11 典型例题:洛谷 P2561 黑白瓷砖
2.12 典型例题:CF 1065F Up and Down the Tree
2.13 典型例题:洛谷 P4128 图的同构计数 - 区间与图
3.1 区间图
3.2 区间约束建模
3.3 线段树优化建图
3.4 前缀和优化建图
3.5 差分约束系统
3.6 区间覆盖问题
3.7 典型例题:CF 786B Legacy
3.8 典型例题:洛谷 P1250 种树
3.9 典型例题:洛谷 P1645 序列
3.10 典型例题:洛谷 P3084 [USACO13OPEN] Photo - 综合例题精讲
- 卡常与调试技巧
- 总结与习题清单
1. 置换环与并查集
1.1 置换的定义
置换(Permutation):一个有限集合到自身的双射。通常用 \(p=(p_1,p_2,\dots,p_n)\) 表示,其中 \(p_i\) 表示位置 \(i\) 上的元素被替换为 \(p_i\)。
例如 \(p=(2,3,1)\) 表示:
- 位置 1 → 2
- 位置 2 → 3
- 位置 3 → 1
专业名词解释:
- 置换:双射 \(p:\{1,\dots,n\}\to\{1,\dots,n\}\)。
- 恒等置换:\(p_i=i\)。
- 置换的阶:最小正整数 \(k\) 使 \(p^k\) 为恒等置换。
- 循环:置换的一种特殊形式,如 \((1\ 2\ 3)\) 表示 \(1\to 2\to 3\to 1\)。
- 循环分解:把置换分解为不相交循环的乘积。
- 不动点:\(p_i=i\) 的位置。
1.2 置换的循环分解
定理
定理 1:任意置换都可以唯一分解为若干不相交循环的乘积。
证明:从任意元素出发,反复应用置换,由于双射,最终必然回到起点,形成一个循环。所有元素被划分成若干循环。∎
示例
\(p=(2,3,1,5,4)\) 的循环分解:
- \(1\to 2\to 3\to 1\),循环 \((1\ 2\ 3)\)
- \(4\to 5\to 4\),循环 \((4\ 5)\)
所以 \(p=(1\ 2\ 3)(4\ 5)\)。
图示
1 -> 2 -> 3 -> 1
4 -> 5 -> 4
1.3 置换环
定义
置换环:置换循环分解后,每个循环对应一个环。环上的元素通过置换互相映射。
性质
性质 1:每个元素恰好属于一个环。
性质 2:环的长度等于该循环中元素的个数。
性质 3:置换的阶 = 所有环长度的最小公倍数(LCM)。
性质 4:置换的不动点对应长度为 1 的环。
性质 5:若置换有 \(c\) 个环,则 \(p\) 与恒等置换的"距离"与 \(c\) 有关。
定理
定理 2:置换 \(p\) 的阶为:
其中 \(l_i\) 是第 \(i\) 个环的长度。
证明:对每个元素 \(x\),经过 \(l_i\) 次置换回到自身。所以阶是 \(l_i\) 的公倍数,最小为 LCM。∎
1.4 用并查集找置换环
方法
把置换 \(p\) 看作边 \(i\to p_i\),用并查集合并。每个连通分量就是一个环。
int fa[N];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x!=y)fa[x]=y;
}
// 主函数
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++)
{
unite(i,p[i]);
}
// 统计环数
int cnt=0;
for(int i=1;i<=n;i++)
{
if(find(i)==i)cnt++;
}
复杂度
- 时间:\(O(n\alpha(n))\)。
- 空间:\(O(n)\)。
注意
并查集只能判断两个元素是否在同一环,不能直接得到环的长度。若要环长度,需遍历。
// 统计每个环的大小
vector<int>sz(n+1,0);
for(int i=1;i<=n;i++)sz[find(i)]++;
1.5 置换环的性质与定理
定理 3(置换环与交换次数)
把一个置换分解为环,若环长度为 \(l\):
- 长度为 1 的环需要 0 次交换。
- 长度为 \(l\) 的环需要 \(l-1\) 次交换还原为恒等。
证明:每次交换可以拆开一个环或减少环长度。∎
推论:把排列 \(p\) 还原为恒等所需最少交换次数:
其中 \(c\) 是环的个数(包括长度为 1 的环)。
定理 4(排列的逆序对与环)
排列的逆序对数的奇偶性 = \(n-c\) 的奇偶性。
证明:每次交换改变逆序对奇偶性。∎
定理 5(置换环与图论)
若把置换 \(p\) 看作有向图 \(i\to p_i\),则每个节点出度为 1,入度为 1,图由若干环组成。
1.6 典型例题:P1330 封锁阳光大学(并查集)
题意:给定无向图,给每个点染黑白两色,相邻点颜色不同。求最少染色数。
做法:二分图判定 + 并查集(扩展域)。
代码:
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
int fa[N*2],sz[N*2];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y)return;
fa[x]=y;
sz[y]+=sz[x];
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=2*n;i++)
{
fa[i]=i;
sz[i]=1;
}
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
unite(u,v+n);
unite(v,u+n);
}
for(int i=1;i<=n;i++)
{
if(find(i)==find(i+n))
{
cout<<"Impossible\n";
return 0;
}
}
int ans=0;
vector<bool>vis(n+1,false);
for(int i=1;i<=n;i++)
{
int a=find(i);
int b=find(i+n);
if(vis[a]||vis[b])continue;
vis[a]=vis[b]=true;
ans+=min(sz[a],sz[b]);
}
cout<<ans<<"\n";
return 0;
}
1.7 典型例题:CF 500B New Year Permutation
题意:给定排列 \(p\) 和交换矩阵 \(A\),\(A_{ij}=1\) 表示可以交换 \(p_i,p_j\)。求字典序最小的排列。
做法:并查集合并可交换的位置,每个连通分量内排序。
代码:
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n;
int p[N];
int fa[N];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x!=y)fa[x]=y;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p[i];
fa[i]=i;
}
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
for(int j=1;j<=n;j++)
{
if(s[j-1]=='1')unite(i,j);
}
}
vector<vector<int>>groups(n+1);
for(int i=1;i<=n;i++)groups[find(i)].push_back(i);
for(int i=1;i<=n;i++)
{
if(groups[i].empty())continue;
vector<int>vals;
for(int x:groups[i])vals.push_back(p[x]);
sort(vals.begin(),vals.end());
sort(groups[i].begin(),groups[i].end());
for(int j=0;j<groups[i].size();j++)
{
p[groups[i][j]]=vals[j];
}
}
for(int i=1;i<=n;i++)cout<<p[i]<<" ";
cout<<"\n";
return 0;
}
1.8 典型例题:洛谷 P1525 关押罪犯(扩展域并查集)
题意:\(n\) 个罪犯,\(m\) 对冲突,把罪犯分到两个监狱,使最大冲突值最小。
做法:按冲突值从大到小排序,用扩展域并查集判断能否分到不同监狱。
代码:
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
struct Node
{
int u,v,w;
};
Node e[N];
int fa[N*2];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
x=find(x);
y=find(y);
if(x!=y)fa[x]=y;
}
bool cmp(Node a,Node b)
{
return a.w>b.w;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>e[i].u>>e[i].v>>e[i].w;
sort(e+1,e+m+1,cmp);
for(int i=1;i<=2*n;i++)fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=e[i].u;
int v=e[i].v;
if(find(u)==find(v))
{
cout<<e[i].w<<"\n";
return 0;
}
unite(u,v+n);
unite(v,u+n);
}
cout<<0<<"\n";
return 0;
}
1.9 典型例题:洛谷 P2024 食物链(带权并查集)
题意:三种动物 A、B、C,A 吃 B,B 吃 C,C 吃 A。给定关系,判断矛盾数。
做法:带权并查集,维护到根的距离模 3。
代码:
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,k;
int fa[N],d[N];
int find(int x)
{
if(fa[x]==x)return x;
int root=find(fa[x]);
d[x]=(d[x]+d[fa[x]])%3;
return fa[x]=root;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)fa[i]=i;
int ans=0;
while(k--)
{
int op,x,y;
cin>>op>>x>>y;
if(x>n||y>n)
{
ans++;
continue;
}
int rx=find(x);
int ry=find(y);
if(op==1)
{
if(rx==ry&&d[x]!=d[y])ans++;
else if(rx!=ry)
{
fa[rx]=ry;
d[rx]=(d[y]-d[x]+3)%3;
}
}
else
{
if(rx==ry&&(d[x]-d[y]+3)%3!=1)ans++;
else if(rx!=ry)
{
fa[rx]=ry;
d[rx]=(d[y]-d[x]+4)%3;
}
}
}
cout<<ans<<"\n";
return 0;
}
2. 置换环进阶与计数
2.1 置换的乘法
定义
两个置换 \(p,q\) 的乘积 \(p\circ q\) 定义为:
示例
\(p=(2,3,1)\),\(q=(1,3,2)\):
- \((p\circ q)(1)=p(q(1))=p(1)=2\)
- \((p\circ q)(2)=p(q(2))=p(3)=1\)
- \((p\circ q)(3)=p(q(3))=p(2)=3\)
所以 \(p\circ q=(2,1,3)\)。
2.2 置换的逆
定义
置换 \(p\) 的逆 \(p^{-1}\) 满足 \(p\circ p^{-1}=p^{-1}\circ p=\text{id}\)。
代码
for(int i=1;i<=n;i++)inv[p[i]]=i;
2.3 置换的阶
定义
置换 \(p\) 的阶是最小正整数 \(k\) 使 \(p^k=\text{id}\)。
公式
示例
\(p=(1\ 2\ 3)(4\ 5)\),环长度 3 和 2,阶为 \(\text{lcm}(3,2)=6\)。
代码
int order(int p[],int n)
{
vector<bool>vis(n+1,false);
int res=1;
for(int i=1;i<=n;i++)
{
if(vis[i])continue;
int len=0;
int x=i;
while(!vis[x])
{
vis[x]=true;
x=p[x];
len++;
}
res=lcm(res,len);
}
return res;
}
2.4 循环长度的 LCM
定理
定理 6:置换 \(p\) 的阶等于所有环长度的 LCM。
证明:见 1.3。∎
应用
- 判断置换何时回到恒等。
- 计算循环节。
典型例题:P2561 黑白瓷砖
题意:\(n\times n\) 瓷砖,黑白染色,旋转后相同视为同一种,求方案数。
做法:Polya 定理 + 置换环。
2.5 置换计数
问题
求 \(n\) 个元素的置换中,有 \(k\) 个环的置换个数。
公式(第一类斯特林数)
其中 \({n\brack k}\) 表示 \(n\) 个元素恰好有 \(k\) 个环的置换数。
代码
int stirling[N][N];
void init(int n)
{
stirling[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
stirling[i][j]=stirling[i-1][j-1]+(i-1)*stirling[i-1][j];
}
}
}
示例
\(n=3\):
- 1 个环:2 个((1 2 3), (1 3 2))
- 2 个环:3 个
- 3 个环:1 个(恒等)
2.6 Burnside 引理
定理
Burnside 引理:设群 \(G\) 作用在集合 \(X\) 上,不同轨道数(即不同染色方案数)为:
其中 \(X^g\) 是在 \(g\) 作用下不变的元素集合。
应用
- 计算旋转、翻转等价下的染色方案数。
- 计算图的同构数。
2.7 Polya 定理
定理
Polya 定理:若用 \(m\) 种颜色染色,群 \(G\) 作用在 \(n\) 个位置上,则不同染色方案数:
其中 \(c(g)\) 是置换 \(g\) 的环个数。
典型例题:洛谷 P4980 Polya 定理
题意:用 \(n\) 种颜色给 \(n\) 个珠子的项链染色,旋转等价,求方案数。
代码:
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
const int inf=1e18;
int qpow(int a,int b,int p)
{
int res=1;
while(b)
{
if(b&1)res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
int phi(int n)
{
int res=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
res=res/i*(i-1);
while(n%i==0)n/=i;
}
}
if(n>1)res=res/n*(n-1);
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
int ans=0;
for(int d=1;d*d<=n;d++)
{
if(n%d)continue;
ans=(ans+phi(d)*qpow(n,n/d,mod))%mod;
if(d*d!=n)ans=(ans+phi(n/d)*qpow(n,d,mod))%mod;
}
ans=ans*qpow(n,mod-2,mod)%mod;
cout<<ans<<"\n";
}
return 0;
}
2.8 置换开方
问题
给定置换 \(p\),求置换 \(q\) 使 \(q^2=p\)。
做法
- 分解 \(p\) 为环。
- 对每个环:
- 若环长度为奇数,开方后仍为一个环。
- 若环长度为偶数,需要两个长度相同的环配对。
代码思路
// 分解环
vector<vector<int>>cycles;
// 对奇数长度环:q[i]=p[p[i]]
// 对偶数长度环:两两配对
典型例题:CF 1065F Up and Down the Tree
2.9 置换环上的 DP
问题
在置换环上做 DP,如求最长路径、方案数。
做法
把环拆成链,用环形 DP。
典型例题:洛谷 P4128 图的同构计数
2.10 典型例题:洛谷 P4980 Polya 定理
见 2.7。
2.11 典型例题:洛谷 P2561 黑白瓷砖
题意:\(n\times n\) 瓷砖,黑白染色,旋转后相同视为同一种,求方案数。
做法:Polya 定理,群为 4 个旋转。
// 旋转 0°:n*n 个环
// 旋转 90°:ceil(n*n/4) 个环
// 旋转 180°:ceil(n*n/2) 个环
// 旋转 270°:ceil(n*n/4) 个环
2.12 典型例题:CF 1065F Up and Down the Tree
题意:树上每个节点有深度,叶子可以跳到祖先,求最多访问叶子数。
做法:树形 DP + 置换环。
2.13 典型例题:洛谷 P4128 图的同构计数
题意:求 \(n\) 个点的无向图在置换群作用下的不同构数。
做法:Polya 定理 + 置换环。
3. 区间与图
3.1 区间图
定义
区间图(Interval Graph):每个节点对应一个区间,两个节点有边当且仅当区间相交。
性质
- 区间图是弦图。
- 最大团 = 最大重叠区间数。
- 最小染色数 = 最大团大小。
应用
- 任务调度。
- 资源分配。
3.2 区间约束建模
问题
给定若干区间约束,如"区间 \([l,r]\) 内至少选 \(k\) 个点",求最少选点数。
做法
差分约束系统。设 \(s_i\) 为前 \(i\) 个位置选点数:
- \(s_i-s_{i-1}\ge 0\)
- \(s_i-s_{i-1}\le 1\)
- \(s_r-s_{l-1}\ge k\)
转化为最短路。
典型例题:洛谷 P1250 种树
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
vector<pair<int,int>>g[N];
int dist[N];
bool inq[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
g[i-1].push_back({i,0});
g[i].push_back({i-1,-1});
}
for(int i=1;i<=m;i++)
{
int l,r,k;
cin>>l>>r>>k;
g[l-1].push_back({r,k});
}
for(int i=0;i<=n;i++)dist[i]=-inf;
dist[0]=0;
queue<int>q;
q.push(0);
inq[0]=true;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for(auto e:g[u])
{
int v=e.fi;
int w=e.se;
if(dist[v]<dist[u]+w)
{
dist[v]=dist[u]+w;
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
}
cout<<dist[n]<<"\n";
return 0;
}
3.3 线段树优化建图
问题
点 \(u\) 向区间 \([l,r]\) 内所有点连边。
做法
建立线段树,用线段树节点作为虚点。
详细实现
两棵线段树:
- 入树(in-tree):父节点向子节点连边(向下)。
- 出树(out-tree):子节点向父节点连边(向上)。
编号:
int id_in(int p){return p;} // 入树节点
int id_out(int p){return p+4*n;} // 出树节点
int id_leaf(int x){return x+8*n;} // 原图点
建树:
void build(int p,int l,int r)
{
if(l==r)
{
add(id_in(p),id_leaf(l),0);
add(id_leaf(l),id_out(p),0);
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
add(id_in(p),id_in(p<<1),0);
add(id_in(p),id_in(p<<1|1),0);
add(id_out(p<<1),id_out(p),0);
add(id_out(p<<1|1),id_out(p),0);
}
点 → 区间:
void update_in(int p,int l,int r,int ql,int qr,int u,int w)
{
if(ql<=l&&r<=qr)
{
add(u,id_in(p),w);
return;
}
int mid=(l+r)>>1;
if(ql<=mid)update_in(p<<1,l,mid,ql,qr,u,w);
if(qr>mid)update_in(p<<1|1,mid+1,r,ql,qr,u,w);
}
区间 → 点:
void update_out(int p,int l,int r,int ql,int qr,int u,int w)
{
if(ql<=l&&r<=qr)
{
add(id_out(p),u,w);
return;
}
int mid=(l+r)>>1;
if(ql<=mid)update_out(p<<1,l,mid,ql,qr,u,w);
if(qr>mid)update_out(p<<1|1,mid+1,r,ql,qr,u,w);
}
典型例题:CF 786B Legacy
见 2.5(前文)。
3.4 前缀和优化建图
问题
点 \(i\) 向 \(1\sim i-1\) 连边。
做法
建立前缀和点 \(pre_i\),\(pre_i\to pre_{i-1}\),点 \(i\to pre_{i-1}\)。
3.5 差分约束系统
定义
差分约束系统:一组形如 \(x_j-x_i\le c_k\) 的不等式。
转化为最短路
- 边 \(i\to j\) 权 \(c_k\)。
- 求最短路,\(dist[j]\le dist[i]+c_k\)。
- 若存在负环,无解。
典型例题:洛谷 P5960 差分约束
#include<bits/stdc++.h>
#define int long long
#define COUT cout<<"\n------------------------------------------------------------------------------------------\n";
#define fi first
#define se second
#define be begin()
#define en end()
using namespace std;
const int N=1e5+5;
const int mod=998244353;
const int inf=1e18;
int n,m;
vector<pair<int,int>>g[N];
int dist[N],cnt[N];
bool inq[N];
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
g[v].push_back({u,w});
}
for(int i=1;i<=n;i++)
{
g[0].push_back({i,0});
}
for(int i=0;i<=n;i++)dist[i]=inf;
dist[0]=0;
queue<int>q;
q.push(0);
inq[0]=true;
while(!q.empty())
{
int u=q.front();
q.pop();
inq[u]=false;
for(auto e:g[u])
{
int v=e.fi;
int w=e.se;
if(dist[v]>dist[u]+w)
{
dist[v]=dist[u]+w;
cnt[v]=cnt[u]+1;
if(cnt[v]>n)
{
cout<<"NO\n";
return 0;
}
if(!inq[v])
{
inq[v]=true;
q.push(v);
}
}
}
}
for(int i=1;i<=n;i++)cout<<dist[i]<<" ";
cout<<"\n";
return 0;
}
3.6 区间覆盖问题
问题
给定若干区间,选最少点覆盖所有区间。
做法
贪心:按右端点排序,每次选右端点。
典型例题:洛谷 P1645 序列
3.7 典型例题:CF 786B Legacy
见 3.3。
3.8 典型例题:洛谷 P1250 种树
见 3.2。
3.9 典型例题:洛谷 P1645 序列
题意:给定若干区间约束,求满足条件的最少选点数。
做法:差分约束。
3.10 典型例题:洛谷 P3084 [USACO13OPEN] Photo
题意:给定若干区间,每个区间恰好有一个特殊点,求最多特殊点数。
做法:差分约束 + 最短路。
4. 综合例题精讲
4.1 P1330 封锁阳光大学
见 1.6。
4.2 CF 500B New Year Permutation
见 1.7。
4.3 P1525 关押罪犯
见 1.8。
4.4 P2024 食物链
见 1.9。
4.5 P4980 Polya 定理
见 2.7。
4.6 P2561 黑白瓷砖
见 2.11。
4.7 P1250 种树
见 3.2。
4.8 CF 786B Legacy
见 3.3。
5. 卡常与调试技巧
5.1 卡常
- 用
vector存图。 - 用数组,不用指针。
- 用快读。
- 并查集路径压缩 + 按秩合并。
- 线段树建图注意节点编号。
- 差分约束用 SPFA 时注意负环。
5.2 常见错误
- 并查集忘记初始化。
- 置换环长度统计错误。
- Polya 定理环数计算错误。
- 差分约束边方向错误。
- 线段树建图节点数不够。
- 置换开方偶数环处理错误。
5.3 调试方法
- 小数据暴力对拍。
- 打印置换环。
- 检查并查集连通性。
- 检查差分约束最短路。
6. 总结与习题清单
6.1 核心要点
- 置换环:循环分解,并查集找环。
- 置换计数:第一类斯特林数,Burnside 引理,Polya 定理。
- 区间与图:区间图、差分约束、线段树建图。
6.2 习题清单
| 题目 | 难度 | 考点 |
|---|---|---|
| P1330 | 中等 | 并查集+二分图 |
| CF 500B | 中等 | 并查集+排序 |
| P1525 | 中等 | 扩展域并查集 |
| P2024 | 中等 | 带权并查集 |
| P4980 | 较难 | Polya 定理 |
| P2561 | 较难 | Polya 定理 |
| P4128 | 难 | 图的同构计数 |
| P1250 | 中等 | 差分约束 |
| P1645 | 中等 | 差分约束 |
| P3084 | 较难 | 差分约束 |
| CF 786B | 较难 | 线段树建图 |
| P5960 | 模板 | 差分约束 |
6.3 推荐练习顺序
P1330 → CF 500B → P1525 → P2024 → P1250 → P1645 → P5960 → CF 786B → P4980 → P2561 → P4128 → P3084。
结语
置换环与并查集、置换环进阶与计数、区间与图是竞赛中重要的高级技巧。掌握它们需要理解:
- 置换的循环分解。
- 并查集找环。
- 第一类斯特林数。
- Burnside 引理与 Polya 定理。
- 差分约束系统。
- 线段树优化建图。
推荐练习顺序:从基础并查集开始,逐步深入 Polya 定理和线段树建图。
祝你在 OI/IOI 之路上越走越远!
本文来自博客园,作者:Aries2011,转载请注明原文链接:https://www.cnblogs.com/Aries2011/p/23131032

浙公网安备 33010602011771号