2 条题解
-
0
题解
思路分析
这是一道 最小割 + 点权转边权 的网络流问题。
问题建模
- 给定一个无向图,每个点有监控费用
- 需要选择最小费用的点集,使得从起点到终点的所有路径都经过至少一个被监控的点
- 输出被监控的点集
核心思路
1. 最小割模型
这是一个经典的最小点割问题:
- 源点 = 港口(起点)
- 汇点 = 仓库(终点)
- 目标:找到最小费用的点集,删除后 和 不连通
2. 点权转边权
网络流的最小割是边割,而题目要求点割。转化方法:
拆点技巧:
- 将每个点 拆成两个点: 和
- 连接有向边 ,容量为点 的费用
- 原图中的边 转化为 和 ,容量为
3. 最小割求解
- 使用 Dinic 算法求最小割
- 最小割 = 最大流
4. 构造方案
求出最小割后,找到被割的边(容量非零但残量为0的边):
- 如果边 被割,说明点 被监控
- 通过 DFS 找到从源点可达的点集
- 被割的点 = 在 中的 但不在 中的
算法步骤
-
拆点建图:
- 点 拆成 和
- 连边 ,容量 = 费用
- 原边 转化为 和 ,容量 =
-
最大流算法(Dinic):
- BFS 分层
- DFS 增广
- 求出最大流(= 最小割)
-
DFS 找割边:
- 从源点 DFS,找到所有可达点
- 输出满足条件的点
复杂度分析
- Dinic 时间复杂度:
- 总时间复杂度:
- 空间复杂度:
#include<bits/stdc++.h> #define ll long long #define rll register ll #define ri register int #define fo(i,x,y) for(ri i=(x);i<=(y);++i) #define fu(i,x,y) for(ri i=(x);i<(y);++i) #define fd(i,x,y) for(ri i=(x);i>=(y);--i) #define pb push_back #define pii pair<int,int> #define fi first #define se second using namespace std; const int N=205,M=2e5+5,inf=2147483647; int n,m,cnt,S,T,tt=1,to[M],nt[M],hd[M],v[M]; int cur[M],dis[M],q[M],vis[M]; inline void add(ri x,ri y,ri z){ to[++tt]=y;nt[tt]=hd[x];hd[x]=tt;v[tt]=z; } inline void link(ri x,ri y,ri z){ add(x,y,z);add(y,x,0); } inline int bfs(){ ri h=0,t=1; fo(i,1,2*n)cur[i]=hd[i],dis[i]=inf; dis[S]=0;q[1]=S; while(h<t){ ri x=q[++h]; for(ri i=hd[x];i;i=nt[i]){ ri y=to[i]; if(dis[y]==inf&&v[i]){ dis[y]=dis[x]+1; q[++t]=y; } } } return dis[T]!=inf; } inline int dfs(ri x,ri fw){ if(x==T||!fw)return fw; ri nd=0; for(ri i=cur[x];i&&fw;i=nt[i]){ ri y=to[i];cur[x]=i; if(dis[y]==dis[x]+1&&v[i]){ ri Fw=dfs(y,min(v[i],fw)); if(!Fw)dis[y]=inf; else{ nd+=Fw;fw-=Fw; v[i]-=Fw;v[i^1]+=Fw; } } } return nd; } inline void dfs2(ri x){ vis[x]=1; for(ri i=hd[x];i;i=nt[i]){ ri y=to[i]; if(!vis[y]&&v[i])dfs2(y); } } int main(){ scanf("%d%d",&n,&m); scanf("%d%d",&S,&T);T+=n; fo(i,1,n){ ri x;scanf("%d",&x); link(i,i+n,x); } fo(i,1,m){ ri x,y;scanf("%d%d",&x,&y); link(x+n,y,inf); link(y+n,x,inf); } while(bfs()){ dfs(S,inf); } dfs2(S); for(ri i=2;i<=2*n;i+=2)if(!vis[to[i]]&&vis[to[i^1]])printf("%d ",i/2); } -
0
题解
把“挑选最小费用的收费站集合拦截所有从港口到仓库的路径”建模成最小割问题:每个收费站拆成“进入点”与“离开点”,两者之间连一条容量为管控成本的边;原图中的无向公路则在拆点后变成两条容量为无穷的有向边。这样从源点(港口邻近收费站的入口)到汇点(仓库邻近收费站的出口)的任意 - 路径都必须经过某条“进入→离开”的有容量边,切断这些边的最小总代价就是答案。
代码使用 Dinic 求最小割:边
link(i, i+n, cost)表示拆点的容量边,link(x+n, y, inf)和link(y+n, x, inf)表示双向公路, 与 分别是港口入口和仓库出口。最大流求完以后,对残量网络从源点做一次 DFS,标记仍能到达的点;若某个收费站的入口可达但出口不可达,则其拆点边被割断,这个收费站位于最小割集合。按节点编号输出这些收费站即可。由于图只有 个结点、 条边,Dinic 足以在极短时间内收敛。
#include<bits/stdc++.h> #define ll long long #define rll register ll #define ri register int #define fo(i,x,y) for(ri i=(x);i<=(y);++i) #define fu(i,x,y) for(ri i=(x);i<(y);++i) #define fd(i,x,y) for(ri i=(x);i>=(y);--i) #define pb push_back #define pii pair<int,int> #define fi first #define se second using namespace std; const int N=205,M=2e5+5,inf=2147483647; int n,m,cnt,S,T,tt=1,to[M],nt[M],hd[M],v[M]; int cur[M],dis[M],q[M],vis[M]; inline void add(ri x,ri y,ri z){ to[++tt]=y;nt[tt]=hd[x];hd[x]=tt;v[tt]=z; } inline void link(ri x,ri y,ri z){ add(x,y,z);add(y,x,0); } inline int bfs(){ ri h=0,t=1; fo(i,1,2*n)cur[i]=hd[i],dis[i]=inf; dis[S]=0;q[1]=S; while(h<t){ ri x=q[++h]; for(ri i=hd[x];i;i=nt[i]){ ri y=to[i]; if(dis[y]==inf&&v[i]){ dis[y]=dis[x]+1; q[++t]=y; } } } return dis[T]!=inf; } inline int dfs(ri x,ri fw){ if(x==T||!fw)return fw; ri nd=0; for(ri i=cur[x];i&&fw;i=nt[i]){ ri y=to[i];cur[x]=i; if(dis[y]==dis[x]+1&&v[i]){ ri Fw=dfs(y,min(v[i],fw)); if(!Fw)dis[y]=inf; else{ nd+=Fw;fw-=Fw; v[i]-=Fw;v[i^1]+=Fw; } } } return nd; } inline void dfs2(ri x){ vis[x]=1; for(ri i=hd[x];i;i=nt[i]){ ri y=to[i]; if(!vis[y]&&v[i])dfs2(y); } } int main(){ scanf("%d%d",&n,&m); scanf("%d%d",&S,&T);T+=n; fo(i,1,n){ ri x;scanf("%d",&x); link(i,i+n,x); } fo(i,1,m){ ri x,y;scanf("%d%d",&x,&y); link(x+n,y,inf); link(y+n,x,inf); } while(bfs()){ dfs(S,inf); } dfs2(S); for(ri i=2;i<=2*n;i+=2)if(!vis[to[i]]&&vis[to[i^1]])printf("%d ",i/2); }
- 1
信息
- ID
- 3243
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 8
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者