平面图:如果能把图\(G\)画在平面上,使得除顶点外,边与边没有交叉,则称\(G\)可以嵌入平面,或称\(G\)为可平面图。可平面图\(G\)的一种边不交叉的画法,称为\(G\)的一种平面嵌入,\(G\)的平面嵌入表示的图称为平面图。
对偶图:对偶图是伴随平面图的一张图,具体来说就是把平面图里的每个面当成一个节点,这些节点之间的边是和原图的边相交的,对偶图边的权值等于原平面图边的权值。
形式化来说:设平面图为\(G\),其对偶图为\(G^{'}\),对于原图中每条边\(e\):
\(1\):若\(e\)在平面图中属于两个平面\(f_1,f_2\),则在对偶图中连边\((f_1^{'},f_2^{'})\)。
\(2\):若\(e\)在平面图中属于一个平面\(f_1\),则在对偶图中连自环\((f_1^{'},f_1^{'})\)。
有如下定理:平面图的最小割等于其对偶图的最短路。
所以当遇到平面图最小割问题时,若点数过多,导致网络流算法不易通过时,可考虑刻画出此平面图的对偶图,并在对偶图上跑最短路即可。
通过如下几道例题来深入理解:
\(P2046\)
https://www.luogu.com.cn/problem/P2046
题解:引理\(1\):将所有海拔赋为\(0\)或\(1\)是最优的。
证明:每次选择海拔绝对值最大的点集,若绝对值大于\(1\),则将点集中所有点海拔根据正负加减\(1\),这样一定不会变劣。如此进行下去,直到图中只有海拔为\(0\)和\(1\)的点。
引理\(2\):最终图一定由一个海拔为\(0\)的连通块和一个海拔为\(1\)的连通块构成。
证明:如果连通块个数\(>2\),则一定有一个连通块被完全包围,将这个被完全包围的连通块改变海拔,这样一定不会变劣。如此进行下去,直到图中只有两个连通块。
考虑这两个连通块代表的权值,正是这个平面图的一个割,但由于此图的点数过多,常规的网络流算法并不能通过。
考虑将平面图转化为对偶图。具体来说,先将起点和终点连上一条虚拟边,这样新增了一个面。令新增的这个面为\(S\),剩余那个无穷大的面为\(T\),按照平面图转对偶图的方式连边,发现新图中\(S\)到\(T\)的任何一条路径,都完成了对原图的一个分割,同时将原图中的\(s\)和\(t\)分割到两个集合中,并且路径长度,即为原图割的大小,于是直接在新建的对偶图中求\(S\)到\(T\)的最短路即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=510,M=2e6+10;
int n,S,T;
int d[M];
int h[M],e[M],w[M],ne[M],idx;
int dl[N][N],dr[N][N],du[N][N],dd[N][N];
bool st[M];
int get(int x,int y){
return (x-1)*n+y;
}
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra(){
memset(d,0x3f,sizeof d);
priority_queue<PII,vector<PII>,greater<PII> > heap;
d[S]=0,heap.push({d[S],S});
while(heap.size()){
int t=heap.top().second;
heap.pop();
if(st[t]) continue;
st[t]=1;
for(int i=h[t]; i!=-1; i=ne[i]){
int j=e[i];
if(st[j]) continue;
if(d[t]+w[i]<d[j]){
d[j]=d[t]+w[i];
heap.push({d[j],j});
}
}
}
return d[T];
}
int main(){
memset(h,-1,sizeof h);
cin >> n;
for(int i=1; i<=n+1; i++)
for(int j=1; j<=n; j++) cin >> dr[i][j];
for(int i=1; i<=n; i++)
for(int j=1; j<=n+1; j++) cin >> dd[i][j];
for(int i=1; i<=n+1; i++)
for(int j=1; j<=n; j++) cin >> dl[i][j];
for(int i=1; i<=n; i++)
for(int j=1; j<=n+1; j++) cin >> du[i][j];
S=n*n+1,T=n*n+2;
for(int i=1; i<=n; i++) add(S,get(i,1),dd[i][1]);
for(int i=1; i<=n; i++) add(get(i,n),T,dd[i][n+1]);
for(int i=1; i<=n; i++) add(S,get(n,i),dr[n+1][i]);
for(int i=1; i<=n; i++) add(get(1,i),T,dr[1][i]);
for(int i=1; i<=n; i++)
for(int j=1; j<=n-1; j++) add(get(i,j),get(i,j+1),dd[i][j+1]);
for(int i=1; i<=n; i++)
for(int j=2; j<=n; j++) add(get(i,j),get(i,j-1),du[i][j]);
for(int i=1; i<=n-1; i++)
for(int j=1; j<=n; j++) add(get(i,j),get(i+1,j),dl[i+1][j]);
for(int i=2; i<=n; i++)
for(int j=1; j<=n; j++) add(get(i,j),get(i-1,j),dr[i][j]);
cout << dijkstra() << endl;
return 0;
}
\(P4001\)
https://www.luogu.com.cn/problem/P4001
题解:同样发现所求的即为最小分割,仿照上一题做法即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=5e6+10;
int n,m,S,T;
int d[N];
int h[N],e[N],w[N],ne[N],idx;
bool st[N];
int get(int x,int y){
if(x>n-1||y<1) return S;
if(x<1||y>2*(m-1)) return T;
return (x-1)*2*(m-1)+y;
}
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra(){
memset(d,0x3f,sizeof d);
priority_queue<PII,vector<PII>,greater<PII> > heap;
d[S]=0,heap.push({d[S],S});
while(heap.size()){
int t=heap.top().second;
heap.pop();
if(st[t]) continue;
st[t]=1;
for(int i=h[t]; i!=-1; i=ne[i]){
int j=e[i];
if(st[j]) continue;
if(d[t]+w[i]<d[j]){
d[j]=d[t]+w[i];
heap.push({d[j],j});
}
}
}
return d[T];
}
int main(){
memset(h,-1,sizeof h);
cin >> n >> m;
S=(n-1)*2*(m-1)+1,T=(n-1)*2*(m-1)+2;
for(int i=1; i<=n; i++)
for(int j=1; j<=m-1; j++){
int v;
cin >> v;
if(i>=2&&i<=n-1) add(get(i-1,2*j-1),get(i,2*j),v);
add(get(i,2*j),get(i-1,2*j-1),v);
}
for(int i=1; i<=n-1; i++)
for(int j=1; j<=m; j++){
int v;
cin >> v;
add(get(i,2*(j-1)),get(i,2*(j-1)+1),v);
if(j>=2&&j<=m-1) add(get(i,2*(j-1)+1),get(i,2*(j-1)),v);
}
for(int i=1; i<=n-1; i++)
for(int j=1; j<=m-1; j++){
int v;
cin >> v;
add(get(i,2*j-1),get(i,2*j),v);
add(get(i,2*j),get(i,2*j-1),v);
}
cout << dijkstra() << endl;
return 0;
}
标签:idx,get,int,平面图,heap,对偶
From: https://www.cnblogs.com/lastxuans/p/18677130