摘要
好博客
本文主要复习一年前学过的网络流,将以luogu,牛客,atcoder等大量题目作为复习
网络流难点在建边,只会板子是没有用的,需要大量的刷题
建边的要求是每一条流量与题意中的策略一一对应
知识点
技巧
拆点限流
酒店之王
signed main(){
memset(h,-1,sizeof h);
S=0,T=N-1;
int p,q,x;
cin>>n>>p>>q;
for(int i=1;i<=p;i++)add(S,i+300,1);
for(int i=1;i<=q;i++)add(i+400,T,1);
for(int i=1;i<=n;i++){
for(int j=1;j<=p;j++){
cin>>x;
if(x)add(j+300,i,1);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=q;j++){
cin>>x;
if(x)add(i+100,j+400,1);
}
}
for(int i=1;i<=n;i++)add(i,i+100,1);
cout<<dinic();
}
P1345 [USACO5.4]奶牛的电信Telecowmunication
80分
错误分析,这道题不是让我们删边,而是删点
signed main(){
memset(h,-1,sizeof h);
S=0,T=N-1;
int c1,c2;
cin>>n>>m>>c1>>c2;
add(S,c1,1e9),add(c2,T,1e9);
for(int i=1;i<=m;i++){
int o=i+n;
int x,y;
cin>>x>>y;
add(x,y,1),add(y,x,1);
}
cout<<dinic();
}
100分
最小割的意思是,求最小的代价使得源点S和汇点T不连通
容量就是代价,所以c1/c2连向c1/c2+n的那条边不能像别的普通点那样代价为1
题目说了这两个点是不能删的
ac了这道题之后
我们考虑几个扩展问题:
删边怎么做?
删的代价不是1而是w[i]怎么做?
既可以删点也可以删边怎么做?
题目再给出k个关键点表示不准删怎么做(概率很大会出)
signed main(){
memset(h,-1,sizeof h);
S=0,T=N-1;
int c1,c2;
cin>>n>>m>>c1>>c2;
add(S,c1,1e9),add(c2+n,T,1e9);
// for(int i=1;i<=n;i++)add(i,i+n,1);//不对
for(int i=1;i<=n;i++){
if(i==c1||i==c2)add(i,i+n,1e9);
else add(i,i+n,1);
}
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(x+n,y,1e9);
add(y+n,x,1e9);
}
cout<<dinic();
}
最长递增子序列问题
这是又一种策略
在跑出来的dp值上建图
如果是1就源点连一条边
如果是第一问的结果maxlen就向汇点连一条边
拆点表示每个值只能用一次
考虑拓展:
将这种策略这种套路应用到another dp上面去?
每个值有特殊的使用次数b[i]?
signed main(){
memset(h,-1,sizeof h);
cin>>n;
S=0,T=N-1;
for(int i=1;i<=n;i++)cin>>a[i];
int L=0;
for(int i=1;i<=n;i++){
g[i]=1;
for(int j=1;j<i;j++){
if(a[i]>=a[j])g[i]=max(g[i],g[j]+1);
}
L=max(L,g[i]);
}
for(int i=1;i<=n;i++){
add(i,n+i);
if(g[i]==1)add(S,i);
if(g[i]==L)add(i+n,T);
for(int j=1;j<i;j++){
if(a[j]<=a[i]&&g[j]+1==g[i])
add(j+n,i);
}
}
int ans=dinic();
cout<<L<<'\n';
cout<<ans<<'\n';
for(int i=0;i<idx;i+=2){
int a=e[i^1],b=e[i];
if(a==S&&b==1)f[i]=1e9;
if(a==1&&b==n+1)f[i]=1e9;
if(a==n&&b==n+n)f[i]=1e9;
if(a==n+n&&b==T)f[i]=1e9;
}
if(L==1)cout<<n;
else cout<<ans+dinic();
}
企鹅游行
建图是很简单的,这里学个还原图的小技巧
void solve(){
memset(h, -1, sizeof h);
idx=0;
cin>>n>>D;
S=0;
int sum=0;
for(int i=1;i<=n;i++)cin>>ice[i].x>>ice[i].y>>ice[i].cnt>>ice[i].ok;
for(int i=1;i<=n;i++){
add(S,i,ice[i].cnt);
add(i,i+n,ice[i].ok);
sum+=ice[i].cnt;
for(int j=1;j<=n;j++){
if(i==j)continue;
if(get_dist(i,j)>D)continue;
add(i+n,j,1e9);
}
}
int ans=0;
for(int i=1;i<=n;i++){
T=i;
for(int j=0;j<idx;j+=2){
f[j]+=f[j^1];
f[j^1]=0;
}
if(dinic()==sum){
cout<<i-1<<" ";
ans++;
}
}
if(ans==0)cout<<"-1"<<'\n';
else cout<<'\n';
}
signed main(){
int _;
cin>>_;
while(_--)solve();
}
利用虚点表示组合关系
一般,题目是不会简单的给出最小割模板,而是会给出m种策略,说在这Ai和Bj组合会得到怎样的收益或者如果Ai和Bj未组合会得到k损失
P1361 小M的作物
考虑A的组合收益,建一个虚点L,S向L连容量为A的边,L向组合点集{x}连边1e9
当且仅当这些点集删掉与T相连的边,才会获得A的收益
换句话说
,如果存在一条边从S->x->T
则说明这个点既属于耕地A又属于耕地B,不合法
如果存在一条边属于S->L->x->T同理
好好理解最小割的含义,断边的代价
一般策略与条件就是不可割边1e9,策略的收益与S连边是可割边,容量是割掉这条边的代价
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
memset(h,-1,sizeof h);
S=0,T=N-1;
int sum=0;
cin>>n;
for(int i=1;i<=n;i++){
int x;
cin>>x;
add(S,i,x);
sum+=x;
}
for(int i=1;i<=n;i++){
int x;
cin>>x;
add(i,T,x);
sum+=x;
}
cin>>m;
int o=n;
while(m--){
int L=++o;
int R=++o;
int k,x,A,B;
cin>>k>>A>>B;
sum+=A,sum+=B;
add(S,L,A),add(R,T,B);
while(k--){
cin>>x;
add(L,x,2e9);
add(x,R,2e9);
}
}
cout<<sum-dinic();
}
时间轴建图
P3980 [NOI2008] 志愿者招募
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
memset(h,-1,sizeof h);
cin>>n>>m;
S=0,T=N-1;
vector<int>a(n+1);
add(S,1,1e9,0);add(n+1,T,1e9,0);
for(int i=1;i<=n;i++){
cin>>a[i];
add(i,i+1,1e9-a[i],0);
//第i天需要a[i]的流量,必然会有流量往志愿者的边上走
}
while(m--){
int l,r,c;
cin>>l>>r>>c;
add(l,r+1,1e9,c);
//花费c的代价能够使1流量从l到达r+1
}
int flow,cost;
EK(flow,cost);
// cout<<flow<<" "<<cost;
cout<<cost;
}
abc274_g
题意:n*m的矩阵有障碍物,选择若干条单向激光照射到全部的点,激光会被障碍物挡住,求最短激光数。
该问题等价于求若干个连通块,贪吃蛇走完所需的最少扭头数
#include<bits/stdc++.h>
using namespace std;
#define INF 1e9
const int N =300*300*2+10,M=N*4;
int h[N],e[M],f[M],ne[M],idx,d[N],cur[N],q[N];
int n,m,S,T;
void add(int a,int b,int c){
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs() // 创建分层图
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false; // 没有增广路
}
int find(int u, int limit) // 在残留网络中增广
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i; // 当前弧优化
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
// int vis[N];
// void dfs(int u){
// vis[u]=1;
// for(int i=h[u];~i;i=ne[i]){
// if(f[i]&&!vis[e[i]])
// dfs(e[i]);
// }
// }
int xx[]={1,-1,0,0};
int yy[]={0,0,1,-1};
int st[220][220];
typedef vector<int>vi;
signed main(){
memset(h,-1,sizeof h);
cin>>n>>m;
vector<string>s(n);
vector<vi> x(n,vi(m,0));
vector<vi> y(n,vi(m,0));
S=n*m*2+1,T=S+1;
for(int i=0;i<n;i++)cin>>s[i];
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(s[i][j]=='#')continue;
int id=i*m+j;
add(S,id,1);
add(id+n*m,T,1);
if(i>0&&s[i-1][j]=='.')x[i][j]=x[i-1][j];
else x[i][j]=id;
if(j>0&&s[i][j-1]=='.')y[i][j]=y[i][j-1];
else y[i][j]=id+n*m;
add(x[i][j],y[i][j],1e9);
}
}
cout<<dinic();
}
牛客练习赛89F
题意:n*n的矩阵,有m个感染源,会不断地扩张,如果给感染源建一堵围墙,代价是1,如果正常格子被感染,代价是c,求最小代价
将被感染的格子放左边,S连边,容量是INF
将未被感染的格子放右边,T连边,容量是c,
格子与格子两两之间连边,容量是1,
其实跑一遍最大流就是答案最小割
#include<bits/stdc++.h>
using namespace std;
#define INF 1e9
const int N =220*220,M=N*10;
int h[N],e[M],f[M],ne[M],idx,d[N],cur[N],q[N];
int n,m,S,T;
void add(int a,int b,int c){
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs() // 创建分层图
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false; // 没有增广路
}
int find(int u, int limit) // 在残留网络中增广
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i; // 当前弧优化
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
// int vis[N];
// void dfs(int u){
// vis[u]=1;
// for(int i=h[u];~i;i=ne[i]){
// if(f[i]&&!vis[e[i]])
// dfs(e[i]);
// }
// }
int xx[]={1,-1,0,0};
int yy[]={0,0,1,-1};
int st[220][220];
signed main(){
memset(h,-1,sizeof h);
int c;
cin>>n>>m>>c;
S=0;
vector id(n,vector<int>(n));
int o=1;
for(int i=0;i<n;i++)for(int j=0;j<n;j++)id[i][j]=o++;
T=o;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(S,id[x][y],INF);
st[x][y]=1;
}
for(int x=0;x<n;x++){
for(int y=0;y<n;y++){
if(!st[x][y])add(id[x][y],T,c);
for(int i=0;i<4;i++){
int dx=x+xx[i];
int dy=y+yy[i];
if(dx>=0&&dx<n&&dy>=0&&dy<n){
add(id[x][y],id[dx][dy],1);
}
}
}
}
// cout<<dinic()-c*m;
cout<<dinic();
}
反悔贪心
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m;
struct node{
int l,r;
bool operator<(const node&t)const{
if(l==t.l)return r>t.r;
return l<t.l;
}
}a[N];
int tmp[N];
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i].l>>a[i].r;
sort(a+1,a+1+n);
int h=1,t=m;
priority_queue<int,vector<int>,greater<int>>q;
vector<int>v;
for(int i=1;i<=n;i++){
int l=a[i].l;
int r=a[i].r;q.push(r);
if(h<=t&&h<=l)h++;
else v.push_back(q.top()),q.pop();
}
sort(v.begin(),v.end());
reverse(v.begin(),v.end());
int ans=0;
for(auto r:v){
if(h<=t&&r<=t)t--;
else ans++;
}
cout<<ans;
}
带有限制的单调栈
class Solution {
public:
vector<int> arrangeBookshelf(vector<int>& order, int limit) {
auto a=order;
int n=a.size();
map<int,int>sum,del,in;
//如果你比我小而且(ans里面的cnt+i后面的cnt)>limit,可以删
// 而且你加进来后in【a[i]】不能大于limit
for(auto x:a)sum[x]++;
vector<int>ans;
for(int i=0;i<n;i++){
sum[a[i]]--;
while(ans.size()&&ans.back()>a[i]&&in[ans.back()]+sum[ans.back()]>limit&&in[a[i]]+1<=limit){
in[ans.back()]--;
ans.pop_back();
}
ans.push_back(a[i]);
in[a[i]]++;
while(ans.size()&&in[ans.back()]>limit){
in[ans.back()]--;
ans.pop_back();
}
}
return ans;
}
};
集美大学校赛题K
#include<bits/stdc++.h>
using namespace std;
#define INF 1e9
const int N =30000,M=N*4;
int h[N],e[M],f[M],ne[M],idx,d[N],cur[N],q[N];
int n,m,S,T;
void add(int a,int b,int c){
e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;
e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs() // 创建分层图
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false; // 没有增广路
}
int find(int u, int limit) // 在残留网络中增广
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i; // 当前弧优化
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--){
idx=0;
memset(h,-1,sizeof h);
cin>>n>>m;
vector<int>a(n+1),p(n+1);
S=0,T=n+2*m+1;
for(int i=1;i<=n;i++)cin>>a[i],p[i]=i,add(S,i,1);
int o=n;
while(m--){
int x,y;
cin>>x>>y;
add(p[x],++o,a[x]);p[x]=o;
add(p[y],++o,a[y]);p[y]=o;
add(p[x],p[y],1);
add(p[y],p[x],1);
}
add(p[1],T,a[1]);
cout<<dinic()<<'\n';
}
}