平面图最短路与对偶图网络流
一个相当厉害的东西啊。
参考原件:IOI 2008 国家集训队论文——周冬。
图片引自
- OI-wiki 平面图
- llmmkk ’s blog
- 论文原件
先给出结论:
平面图最小割等于其对偶图最短路
平面图
平面图,指可以通过画图方式将使得边两两不相交的图。(无向图)
例如:
事实上是:
一些概念:
-
设
是平面图,由 的边将所在的平面划分成若干个区域,每个区域称为
的一个面,其中面积无限的面称为无界面或外部面,面积有限的称为有限面或内部面。包围每个面的所有边组成的回路称为该面的边界,边界的长度称为该面的次数(边数)。显然有次数之和为 -
阶数 也就是
,边数也就是 。 -
若在简单平面图
的任意不相邻顶点间添加边,所得图为非平面图,称 为极大平面图。一个
-
一个
的完全图不是平面图,一个 的完全二分图不是平面图。一个图是平面图当且仅当不存在可以收缩到上述两种情况的子图
-
典型的平面图是网格图。
-
,其中 是面数,欧拉公式,以下也采用 表示阶数边数面数。 -
S-T 平面图:图中的一个点为源点
,另外一个点为汇点 ,且 和 都在图中的无界面的边界上
对偶图
平面图的对偶图也是一个平面图,具体构造方法如下:
-
将每个面视作一个点
-
按照如下方法构造:
- 该边同时属于面
,则连边 - 该边仅属于面
,则连边 。也即自环。 - 注意都是无向边
- 该边同时属于面
性质:
- 对偶图的每一个环对应原图的一个割(一一对应)
- 对偶图与原图边数相同,对偶图点数等于原图面数,可得对偶图面数等于原图点数
建立对偶图与 S-T 平面图关系
容易想到一种构造方法:
-
连接
,边权为零,得到一个附加面,对这样的一个平面图建立对偶图 -
称原本附加面对应点为
,原本无界面对应点为 -
我们让新图的边权等价为容量,则我们所求
的最小割等价于该图包含 的一个边权和最小的环,由于 之间边权为零,因此这个边权和等价于删去 后 的最短路。 -
我们使用 Dijkstra 算法求解最短路即可,复杂度得到大大降低。
实际应用
实际应用中这个平面图常常抽象为网格图,从

然后上右两个边界连向
正向:CSP-S2021 交通规划
简要题意:给定一张网格图,每次给出
容易发现题目即为求解一个割掉边的方案使得所有黑色关键点与白色关键点不连通且割的边边权和最小。
很容易想到暴力网络流建图,可以获得 65 pts。
然后考虑
两者综合可以获得相当可观的分数。
当一般
但是我们受到了一些启发,我们将相邻的同色特殊点合并在一起,然后将合并后两个相邻非同色特殊点的部分依次划分为
我们可以
这个套路很多,可以将其视作括号序列等,利用破环为链后的区间DP可以轻松求解。
最终复杂度
#include<bits/stdc++.h>
using namespace std;
#define pr pair<int,int>
#define mk make_pair
#define N 1005000
int id[505][505],num,n,m,dis[N],vis[N],f[505][505],wl[505][505],wh[505][505],ope[N],d[505][505],Id[N],col[N],cnt,nid[N];//操作涉及点编号
pr waiwei[N];//外围点对应格点
vector<pr >e[N];
void dijkstra(int s){
for(int i=1;i<=num;++i)dis[i]=0x3f3f3f3f,vis[i]=0;
priority_queue<pr >q;
dis[s]=0,q.push(mk(0,s));
while(!q.empty()){
int u=q.top().second;q.pop();
if(vis[u])continue;
vis[u]=1;
for(auto to:e[u]){
int v=to.first,w=to.second;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
// cout<<u<<" "<<v<<' '<<w<<" "<<dis[v]<<"\n";
q.push(mk(-dis[v],v));
}
}
}
}
int dp(int len){
for(int i=1;i<=len;++i)Id[i+len]=Id[i]=i;
for(int i=0;i<=len<<1;++i)for(int j=0;j<=len<<1;++j)f[i][j]=0x3f3f3f3f;
for(int i=1;i<=len<<1;++i)f[i+1][i]=0;
for(int x=2;x<=len;x+=2)for(int l=1,r=x;r<=len<<1;++r,++l){
f[l][r]=f[l+1][r-1]+d[Id[l]][Id[r]];
for(int k=l+1;k<r-1;k+=2)f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
}
int ans=0x3f3f3f3f;
for(int l=1;l<len;++l)ans=min(ans,f[l][l+len-1]);
return ans;
}
void add(int u,int v,int w){
e[u].push_back(mk(v,w));e[v].push_back(mk(u,w));
}
int cost[N];
void build(){
for(int i=1;i<=m;++i)waiwei[++num]=mk(1,i);
for(int i=1;i<=n;++i)waiwei[++num]=mk(i,m);
for(int i=m;i;--i)waiwei[++num]=mk(n,i);
for(int i=n;i;--i)waiwei[++num]=mk(i,1);
for(int i=1;i<=(m+n)*2;++i)cost[i]=0;
for(int i=1;i<n;++i)for(int j=1;j<m;++j)id[i][j]=++num;
for(int i=1;i<n;++i)for(int j=1;j<=m;++j){
if(j==1)add(2*(m+n)-i+1,id[i][j],wl[i][j]);
else if(j==m)add(m+i+1,id[i][j-1],wl[i][j]);
else add(id[i][j],id[i][j-1],wl[i][j]);
}
for(int i=1;i<=n;++i)for(int j=1;j<m;++j){
if(i==1)add(j+1,id[i][j],wh[i][j]);
else if(i==n)add(2*m+n-j+1,id[i-1][j],wh[i][j]);
else add(id[i][j],id[i-1][j],wh[i][j]);
}
}
vector<int>S,T;
void sol(){
cin>>cnt;int flg=1;
for(int i=1;i<=cnt;++i){
int w;cin>>w;cin>>ope[i];cin>>col[ope[i]];cost[ope[i]]=w;
}
for(int i=1;i<=(n+m)<<1;++i){
int j=i+1;if(i==(n+m)<<1)j=1;
add(i,j,cost[i]);cost[i]=0;
// cout<<"Ad "<<i<<' '<<j<<' '<<cost[i]<<"\n";
}
int c=col[ope[1]];
for(int i=1;i<=cnt;++i)flg&=(col[ope[i]]==c);
if(flg){
cout<<"0\n";
}
else {
sort(ope+1,ope+cnt+1);
for(int i=1;i<=cnt;++i)for(int j=1;j<=cnt;++j)d[i][j]=0x3f3f3f3f;
for(int i=1;i<=cnt;++i)ope[i+cnt]=ope[i];int st=0;
for(int i=1;i<=cnt;++i)if(col[ope[i]]!=col[ope[i+1]])st=i+1;
int len=0;
S.clear();T.clear();
for(int i=st;i<st+cnt;++i){
if(col[ope[i]]!=col[ope[i-1]]){
if(col[ope[i]])S.push_back(ope[i]),++len,nid[ope[i]]=len;
else T.push_back(ope[i]),++len,nid[ope[i]]=len;
}
}
for(auto x:S){
dijkstra(x);
for(auto y:T)d[nid[x]][nid[y]]=d[nid[y]][nid[x]]=dis[y];
}
cout<<dp(len)<<"\n";
}
for(int i=1;i<=(n+m<<1);++i){
e[i].pop_back();e[i].pop_back();
}
}
int main(){
freopen("traffic.in","r",stdin);
freopen("traffic.out","w",stdout);
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n>>m;int T;cin>>T;
for(int i=1;i<n;++i)for(int j=1;j<=m;++j)cin>>wl[i][j];
for(int i=1;i<=n;++i)for(int j=1;j<m;++j)cin>>wh[i][j];
build();
while(T--){
sol();
}
}
反向:某模拟赛题
给定一张网格图,边有边权,你可以花费
建立对偶图,相当于每次花费
复杂度玄学。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!