网络流与线性规划 24 题总结

网络流做了那么多,先写一下网络流与线性规划 24 题的回顾与总结。

先粘一下模板:

Dinic:

template<typename Tp, size_t sizn, size_t sizm>
struct netflow
{
int cnt=1, s=sizn-3, t=sizn-2;
Tp val[sizm<<1], dis[sizn];
void link(int u, int v, Tp w)
{
to [++cnt]=v; val [cnt]=w;
nxt[ cnt ]=head[u]; head[ u ]=cnt;
to [++cnt]=u; val [cnt]=0;
nxt[ cnt ]=head[v]; head[ v ]=cnt;
}
const Tp inf=((Tp)INFINITY)>>1;
int head[sizn], to[sizm<<1], nxt[sizm<<1], now[sizm<<1];
int bfs()
{
for(int i=1;i<sizn;i++) dis[i]=inf;
queue<int> q;
q.push(s);
dis[s]=0;
now[s]=head[s];
while (!q.empty())
{
int idx=q.front(); q.pop();
for(int i=head[idx];i;i=nxt[i])
{
int arr=to[i];
if(val[i]>0&&dis[arr]==inf)
{
q.push(arr);
now[arr]=head[arr];
dis[arr]=dis [idx]+1;
if(arr==t) return 1;
}
}
}
return 0;
}
Tp dfs(int idx, Tp sum)
{
if(idx==t) return sum;
Tp k, res=0;
for(int i=now[idx];i&∑i=nxt[i])
{
now[idx]=i;
int arr=to[i];
if(val[i]>0&&(dis[arr]==dis[idx]+1))
{
k=dfs(arr, min(sum, val[i]));
if(k==0) dis[arr]=inf;
val[i]-=k; res+=k;
val[i^1]+=k; sum-=k;
}
}
return res;
}
public:
Tp maxflow()
{
Tp ans=0;
while (bfs()) ans+=dfs(s, inf);
return ans;
}
};

Prime-Dual:

template<typename Tp, size_t sizn, size_t sizm>
struct costflow
{
#define pb __gnu_pbds
typedef pb::priority_queue<pair<Tp, int>,
greater<pair<Tp, int> >,
pb::pairing_heap_tag> pairing_heap;
int cnt=1, s=sizn-2, t=sizn-3;
Tp delta=0, MXflow=0, MNcost=0;
void link(int u, int v, Tp w, Tp f)
{
to [++cnt]=v; cap [cnt]=w;
nxt[ cnt ]=head[u]; head[ u ]=cnt;
cost[cnt ]=f; from[cnt]=u;
to [++cnt]=u; cap [cnt]=0;
nxt[ cnt ]=head[v]; head[ v ]=cnt;
cost[cnt ]=-f; from[cnt]=v;
}
bitset<sizn> vis;
pairing_heap pq;
typename pairing_heap :: point_iterator it[sizn];
Tp val[sizm<<1], dis[sizn], flow[sizn], cost[sizm<<1], cap[sizm<<1];
int head[sizn], to[sizm<<1], nxt[sizm<<1], now[sizm<<1], from[sizm<<1];
bool SPFA()
{
queue<int> q;
memset(dis, 0x3f, sizeof dis);
vis.reset();
dis[t]=0; q.push(t);
vis[t]=1;
while (!q.empty())
{
int u=q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
Tp f=val[i^1], c=cap[i^1], len=cost[i^1];
if(f<c&&dis[v]>dis[u]+len)
{
dis[v]=dis[u]+len;
if(!vis[v]) vis[v]=1, q.push(v);
}
}
}
return dis[s]!=dis[0];
}
void reduce()
{
for(int i=2;i<=cnt;i++) cost[i]+=dis[to[i]]-dis[from[i]];
delta+=dis[s];
}
bool Dijkstra()
{
memset(dis, 0x3f, sizeof dis);
memset(it, 0, sizeof it);
dis[t] = 0;
it[t] = pq.push({dis[t], t});
while(!pq.empty())
{
int u = pq.top().second; pq.pop();
for(int j=head[u];j;j=nxt[j])
{
int v=to[j];
Tp f=val[j^1], c=cap[j^1], len=cost[j^1];
if(f<c&&dis[v]>dis[u]+len)
{
dis[v]=dis[u]+len;
if(it[v]==NULL) it[v]=pq.push({dis[v], v});
else pq.modify(it[v], {dis[v], v});
}
}
}
return dis[s] != dis[0];
}
Tp dfs(int idx, Tp sum)
{
if(idx==t) return sum;
vis[idx]=1;
Tp k, ret=sum;
for(int i=head[idx];i&∑i=nxt[i])
{
int v=to[i];
Tp f=val[i], c=cap[i], len=cost[i];
if(!vis[v]&&f<c&&!len)
{
k=dfs(v, min(ret, c-f));
val[i]+=k;
val[i^1]-=k;
ret-=k;
}
}
return sum-ret;
}
void augment()
{
Tp curflow=0;
vis.reset();
while((curflow=dfs(s, dis[0])))
{
MXflow+=curflow;
MNcost+=curflow*delta;
vis.reset();
}
}
public:
void PrimalDual()
{
if(!SPFA()) return;
reduce(); augment();
while(Dijkstra()) {reduce(); augment();}
}
};

#1 孤岛营救问题

这压根不是网络流...

BFS+状压即可。

推荐新学 BFS 的人来写这个。

#include<bits/stdc++.h>
using namespace std;
#define maxn 102
int mp[maxn][maxn];
#define pos(x, y) ((x-1)*10+(y))
bitset<11> ks[15][15];
int vis[12][12][2049];
int n, m, p;
struct Stat
{
int x, y, t=0;
bitset<11> keys=0;
};
queue<Stat> q;
int dx[]={0, 0, 1, -1};
int dy[]={1, -1, 0, 0};
#define chk(x, y) ((x)&&(y)&&(x<=n)&&(y<=m))
int bfs()
{
Stat tmp={1, 1};
q.push(tmp);
while(!q.empty())
{
tmp=q.front();
q.pop();
int x=tmp.x, y=tmp.y, t=tmp.t;
if(x==n&&y==m) return t;
auto b=tmp.keys;
b|=ks[x][y];
if(vis[x][y][b.to_ulong()]<t) continue;
vis[x][y][b.to_ulong()]=t;
for(int i=0;i<4;i++)
{
if(!chk(x+dx[i], y+dy[i])) continue;
if(!mp[pos(x, y)][pos(x+dx[i], y+dy[i])]) continue;
if(~mp[pos(x, y)][pos(x+dx[i], y+dy[i])]&&
!b[mp[pos(x, y)][pos(x+dx[i], y+dy[i])]]) continue;
q.push({x+dx[i], y+dy[i], t+1, b});
}
}
return -1;
}
int main()
{
memset(mp, -1, sizeof mp);
memset(vis, 0x3f, sizeof vis);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>m>>p;
int k;
cin>>k;
int x1,x2,y1,y2,g, s;
while(k--)
{
cin>>x1>>y1>>x2>>y2>>g;
mp[pos(x1, y1)][pos(x2, y2)]=g;
mp[pos(x2, y2)][pos(x1, y1)]=g;
}
cin>>s;
while(s--)
{
cin>>x1>>y1>>g;
ks[x1][y1][g]=1;
}
cout<<bfs();
}

#2 飞行员配对方案问题

二分图最大匹配。

可以用匈牙利或者 Dinic 或者别的什么网络流算法。

简单讲一下建图。

  • 从原点 S 向每个外籍飞行员连流量为 1 的边。
  • 从每个英国飞行员向汇点 T 连流量为 1 的边。
  • 从每个外籍飞行员向他能配合的英国飞行员连流量为 1 的边。

最大流即为答案。

输出方案的话就遍历每条从外籍飞行员向他能配合的英国飞行员的边,如果满流则计入方案。

link() 函数稍加改造即可。

void link(int u, int v, Tp w)
{
to [++cnt]=v; val [cnt]=w;
nxt[ cnt ]=head[u]; head[ u ]=cnt;
from[cnt]=u; // <-------------------------- 这里
to [++cnt]=u; val [cnt]=0;
nxt[ cnt ]=head[v]; head[ v ]=cnt;
}
netflow<long long, 10000, 10000> nf;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int n, m;
cin>>n>>m;
int i=1;
for(;i<=n;i++) nf.link(nf.s, i, 1);
for(;i<=m;i++) nf.link(i, nf.t, 1);
int x, y;
cin>>x>>y;
while(~x&&~y)
{
nf.link(x, y, 1);
cin>>x>>y;
}
cout<<nf.maxflow()<<'\n';
for(int i=2;i<=nf.cnt;i+=2)
if(!nf.val[i]&&nf.to[i]!=nf.t&&nf.from[i]!=nf.s)
cout<<format("{} {}\n", nf.to[i], nf.from[i]);
}

#3 软件补丁问题

最短路+状压。

todo

#4 负载平衡问题

被贪心爆了...

但是我还是用的最小费用最大流

模拟一下循环的过程即可。

  • 从原点 S 连向货物比平均数多的点,流量为多出来的货物数量,费用为 0
  • 从货物比平均数少的点连向汇点 T,流量为缺少的货物数量,费用为 0
  • 在相邻的两个仓库间连流量为 \infin,费用为 1双向边

比贪心直观(暴论)

costflow<int, 10005, 10005> cf;
int lis[102];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>lis[i];
int per=accumulate(lis+1, lis+1+n, 1)/n;
for(int i=1;i<n;i++)
cf.link(i, i+1, 0x3f3f3f3f, 1),
cf.link(i+1, i, 0x3f3f3f3f, 1);
cf.link(1, n, 0x3f3f3f3f, 1);
cf.link(n, 1, 0x3f3f3f3f, 1);
for(int i=1;i<=n;i++)
{
if(lis[i]>per) cf.link(cf.s, i, lis[i]-per, 0);
if(lis[i]<per) cf.link(i, cf.t, per-lis[i], 0);
}
cf.PrimalDual();
cout<<cf.MNcost;
}

#5 分配问题

最大费用最大流+最小费用最大流。

从源点 T 向每个人连费用为 0 容量为 1 的边,也从每个人向汇点 S 连费用为 0 容量为 1 的边,然后照着题意模拟连边即可。

费用流板子题。

costflow<int, 10000, 10000> cf, xf;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cf.link(cf.s, i, 1, 0),
xf.link(xf.s, i, 1, 0),
cf.link(i+n, cf.t, 1, 0),
xf.link(i+n, xf.t, 1, 0);
for(int i=1;i<=n;i++)
for(int j=1, t;j<=n;j++)
cin>>t, cf.link(i, j+n, 1, -t),
xf.link(i, j+n, 1, t);
cf.PrimalDual();
xf.PrimalDual();
cout<<xf.MNcost<<'\n'<<-cf.MNcost;
}

#6 骑士共存问题

套路题。

在这道题基础上有很多改版,比如长脖子鹿放置

首先黑白染色。(n=4)

1 0 1 0
0 1 0 1
1 0 1 0
0 1 0 1

染色之后考虑将原题转化为最小割问题。

也就是说在棋盘上放满骑士后再考虑扔掉一些棋子。

连边方法如下:

  • 将能互相攻击到的黑白格子互相连边,边权为

  • 从原点 S能放置的白格连边权为 1 的边。

  • 能放置的黑格向汇点 T 连边权为 1 的边。

最后的结果就是 n2mmaxflow

netflow<int, 50000, 1000000> nf;
#define maxn 202
int blk[maxn][maxn];
#define pos(i, j) (((i)-1)*n+(j))
int dx[]={1, -1, 1, -1, 2, -2, 2, -2};
int dy[]={2, 2, -2, -2, 1, 1, -1, -1};
#define chk(i, j) ((i)>0&&(j)>0&&(i)<=n&&(j)<=n)
int main()
{
int n, m;
cin>>n>>m;
for(int i=1, a, b;i<=m;i++)
{
cin>>a>>b;
blk[a][b]=1;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(blk[i][j]) continue;
if((i+j)&1) nf.link(nf.s, pos(i, j), 1);
else {nf.link(pos(i, j), nf.t, 1);continue;}
for(int k=0;k<8;k++)
{
int nx=i+dx[k];
int ny=j+dy[k];
if(!chk(nx, ny)||blk[nx][ny]) continue;
nf.link(pos(i, j), pos(nx, ny), nf.inf);
}
}
cout<<n*n-m-nf.maxflow();
}

本文作者:Jimmy-LEEE

本文链接:https://www.cnblogs.com/redacted-area/p/18084166

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Jimmy-LEEE  阅读(19)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起