网络流复习

摘要

好博客
本文主要复习一年前学过的网络流,将以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';
    }
}

猜你喜欢

转载自blog.csdn.net/supreme567/article/details/127470341