五道粘板子的题的故事

网络流题。

T1 P2764 最小路径覆盖问题

题目大意
题目字面意思
给定一个\(n\)个点\(m\)条边的\(DAG\),求至少需要多少条不相交路径才能覆盖所有点。
\(1≤n≤150,1≤m≤6000\)
Sol
我们先假定初始有\(n\)条路径,每个路径只包含一个顶点。那么原问题就转化成了路径收尾衔接的最大次数\(x\),最终结果就是\(n-x\)
拆点做,把每个点拆成入点和出点,对应有向边就连接\(x\)\(y+n\)。然后再跑一遍二分图最大匹配就可以了。用的比较好写的匈牙利。再不用后面的用不了了
code

#include<bits/stdc++.h>
using namespace std;
const int maxn=160,maxm=6010;
inline int read()
{
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return x;
}
struct edge
{
    int to,next;
}e[maxm];
int h[maxn<<1],ei;
inline void add(int x,int y)
{
    e[++ei]=(edge){y,h[x]};
    h[x]=ei;return;
}
int pipei[maxn<<1];
bool vis[maxn<<1];
int n,m,ans,st[maxn];
inline bool find(int x)
{
    
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(vis[to])continue;
        vis[to]=1;
        if(!pipei[to]||find(pipei[to]))
        {
            pipei[to]=x;
            pipei[x]=to;
            return 1;
        }
    }
    return 0;
}
inline void xyl()
{
    for(int i=1;i<=n;i++)
    {
        if(!pipei[i])
        {
            memset(vis,0,sizeof(vis));
            ans-=find(i);
        }
    }
    return;
}
inline void print(int x)
{
    printf("%d ",x);
    while(x=pipei[x])
    {
        x-=n;vis[x]=1;
        printf("%d ",x);
    }
    printf("\n");
    return;
}
int main()
{
    ans=n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        add(x,y+n);
    }
    xyl();
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        if(!vis[i])print(i);
    }
    printf("%d\n",ans);
    return 0;
}

T2 P2774 方格取数问题

题目大意
给定一个\(n*m\)的由正整数构成的矩阵,现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
\(1≤n,m≤100,1 \leq a_{i, j} \leq 10^5\)
Sol
先全部取,然后考虑怎么不取哪些格子可以最优,按照相邻关系建图,由于最大流\(=\)最小割,所以直接跑一遍\(Dinic\)求得最大流\(x\),最终答案就是全部数之和减去\(x\)即可。
建图的时候按照黑白染色,源点向白点连流量为白点数值的边,白点向黑点连流量为\(inf\)的边,黑点向汇点连流量为黑点数值的边。
code

#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
int S,T,n,m;
inline int read()
{
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return x;
}
inline bool ing(int x,int y)
{
    if(x>=1&&x<=n&&y>0&&y<=m)return 1;
    return 0;
}
struct edge
{
    int to,next,v;
}e[maxn*maxn<<4];
int h[maxn*maxn],ei=1;
inline void add(int x,int y,int v)
{
    e[++ei]=(edge){y,h[x],v};
    h[x]=ei;return;
}
queue<int>qu;
int dep[maxn*maxn];
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    dep[S]=1;
    qu.push(S);
    while(!qu.empty())
    {
        int x=qu.front();qu.pop();
        for(int i=h[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(dep[to])continue;
            if(!e[i].v)continue;
            dep[to]=dep[x]+1;
            qu.push(to);
        }
    }
    return dep[T]>0;
}
inline int dfs(int x,int maxflow)
{
    if(x==T)return maxflow;
    int flow=0;
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(dep[to]!=dep[x]+1||e[i].v==0)continue;
        int rst=dfs(to,min(maxflow-flow,e[i].v));
        if(rst==0)dep[to]=0;
        e[i].v-=rst;e[i^1].v+=rst;
        flow+=rst;
        if(flow==maxflow)break;
    }
    return flow;
}
int xx[4]={0,0,1,-1},yy[4]={1,-1,0,0},ans;
inline int pos(int x,int y){return (x-1)*m+y;}
inline void dinic()
{
    while(bfs())ans-=dfs(S,2e9);
}
int main()
{
    n=read();m=read();
    S=0;T=n*m+1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            int x=read();
            ans+=x;
            if((i+j)%2==0)
            {
                add(S,pos(i,j),x);
                add(pos(i,j),S,0);
                for(int k=0;k<4;k++)
                {
                    int tox=i+xx[k],toy=j+yy[k];
                    if(ing(tox,toy))
                    {
                        add(pos(i,j),pos(tox,toy),2e9);
                        add(pos(tox,toy),pos(i,j),0);
                    }
                }
            }else
            {
                add(pos(i,j),T,x);
                add(T,pos(i,j),0);
            }
        }
    }
    dinic();
    printf("%d\n",ans);
    return 0;
}

染色都能染歪来我是什么寄吧

T3 P1251 餐巾计划问题

题目大意
给定长为\(N\)天的毛巾计划使用量,每天使用量为\(r_i\),每天产生的每条脏毛巾可以选择快洗,支付\(f\)元在\(m\)天后得到干净毛巾,也可以选择慢洗,支付\(s\)元在\(n\)天后得到干净毛巾。每天都可以以\(p\)元/条的价格购进新毛巾。求\(n\)天所需最少资金。
\(N \leq 2000\)
\(r_i \leq 10^7\)
\(p,f,s,m,n \leq 10^4\)
保证\(f>s,m<n\)
Sol
这费用已经摆的明明白白了,当然是费用流。
把每天分成早上和晚上。
源点向每晚建边,流量\(r_i\),费用\(0\),表示每天产生脏毛巾。
每天早上向汇点建边,流量\(r_i\),费用\(0\),表示每天供应干净毛巾。
每天晚上向第二天晚上建边,流量\(inf\),费用\(0\),表示每天的脏毛巾可以留到明天。
每天晚上向\(m\)天后早上建边,流量\(inf\),费用\(f\),表示拿去快洗的脏毛巾。
每天晚上向\(n\)天后早上建边,流量\(inf\),费用\(s\),表示拿去慢洗的脏毛巾。
源点向每天早上建边,流量\(inf\),费用\(p\),表示购买的毛巾。
建边的时候要注意不能越界。
剩下的就是费用流的板子了,用的\(EK\)算法(听说\(EK\)是有可能会被卡的)。
code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=2e9;
inline int read()
{
    int x=0;char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return x;
}
int n,m,S,T;
struct edge
{
    int to,nxt,v,c;
}e[1000010];
int ei=1,h[4010];
int ans,sum;
int dis[4010],f[4010],mn[4010],u[4010],id[4010];
int a[4010],pp,mm,ff,nn,ss;
void add(int x,int y,int v,int c)
{
    e[++ei]=(edge){y,h[x],v,c};
    h[x]=ei;return;
}
bool SPFA()
{
    queue<int>qu;
    memset(dis,0x3f,sizeof(dis));
    memset(f,0,sizeof(f));
    memset(mn,0x3f,sizeof(mn));
    memset(u,0,sizeof(u));
    memset(id,0,sizeof(id));
    qu.push(S);
    dis[S]=0;
    while(!qu.empty())
    {
        int f1=qu.front();
        qu.pop();
        u[f1]=0;
        for(int i=h[f1];i;i=e[i].nxt)
        {
            int to=e[i].to;
            if(e[i].v==0||dis[to]<=dis[f1]+e[i].c)continue;
            dis[to]=dis[f1]+e[i].c;
            f[to]=f1;id[to]=i;
            mn[to]=min(mn[f1],e[i].v);
            if(u[to]==0)
            {
                qu.push(to);
                u[to]=1;
            }
        }
    }
    return dis[T]<1000000000000;
}
void update()
{
    int nxt=T;
    while(nxt>0)
    {
        e[id[nxt]].v-=mn[T];
        e[id[nxt]^1].v+=mn[T];
        nxt=f[nxt];
    }
    return;
}
void EK()
{
    while(SPFA())
    {
        ans+=mn[T];
        sum+=mn[T]*dis[T];
        update();
    }
    return;
}
signed main()
{
    cin>>n;
    S=0;T=2*n+1;
    for(int i=1;i<=n;i++)
    {
        a[i]=read();add(i,T,a[i],0);add(T,i,0,0);
        add(S,i+n,a[i],0);add(i+n,S,0,0);
        if(i<n)add(i+n,i+n+1,inf,0),add(i+n+1,i+n,0,0);
    }
    pp=read();mm=read();ff=read();nn=read();ss=read();
    for(int i=1;i<=n;i++)
    {
        if(i+mm<=n)add(i+n,i+mm,inf,ff),add(i+mm,i+n,0,-ff);
        if(i+nn<=n)add(i+n,i+nn,inf,ss),add(i+nn,i+n,0,-ss);
        add(S,i,inf,pp);add(i,S,0,-pp);
    }
    EK();
    cout<<sum;
    return 0;
}

据@wwlw佬的说法,我这样跑\(SPFA\)是不优的,不过时限开的\(4s\),所以也还是很容易就过了。

T4 P5458 [BJOI2016]水晶

题目大意
这的题目大意懒得写了。具体看原题干吧……
Sol
题干里面也有提到这个三维就是个假的。对于一个\((x,y,z)\)可以实际存成\((x-z,y-z)\)
后面的内容就和方格取数问题很相似了。将点红黄蓝染色,其中带能量源的点染成红色。
但是按照方格取数的连法计算不了中间点的价值,所以考虑拆点,每个点入点连向出点,流量\(val_i\)
源点连向黄点,流量\(inf\)
黄点连向红点,流量\(inf\)
红点连向蓝点,流量\(inf\)
蓝点连向汇点,流量\(inf\)
这样对于每条源点到汇点的路径就是与\(A\)\(B\)共振的一一对应,这也是红点要在中间的原因。
最终结果就是总价值\(-\)最大流结果。注意能量源的水晶价值有变化。
也不知道为什么,我少加了一句满流提前结束的特判直接\(T\)两个点。
code

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp(x,y) make_pair(x,y)
const int maxn=100010,inf=2e9;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return x*f;
}
map<pair<int,int>,int>node;
struct edge
{
    int to,next,v;
}e[maxn<<4];
int h[maxn],ei=1;
inline void add(int x,int y,int v)
{
    e[++ei]=(edge){y,h[x],v};
    h[x]=ei;
    e[++ei]=(edge){x,h[y],0};
    h[y]=ei;
    return;
}
int n,m,S,T,ans,gpc;
int xx[maxn],yy[maxn],zz[maxn],vv[maxn];
bool vis[maxn];
inline int getz(int x){return ((xx[x]+yy[x]+zz[x])%3+3)%3;}
int dep[maxn];
queue<int>qu;
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    dep[S]=1;qu.push(S);
    while(!qu.empty())
    {
        int x=qu.front();qu.pop();
        for(int i=h[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(dep[to]||e[i].v==0)continue;
            dep[to]=dep[x]+1;
            qu.push(to);
        }
    }
    return dep[T]>0;
}
inline int dfs(int x,int maxflow)
{
    if(x==T)return maxflow;
    int flow=0;
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(dep[to]!=dep[x]+1||e[i].v==0)continue;
        int rst=dfs(to,min(maxflow-flow,e[i].v));
        if(rst==0)dep[to]=0;
        e[i].v-=rst;e[i^1].v+=rst;
        flow+=rst;
        if(flow==maxflow)break;
    }
    return flow;
}
inline void dinic()
{
    while(bfs())ans-=dfs(S,inf);
}
signed main()
{
    n=read();
    S=0;T=2*n+1;
    for(int i=1;i<=n;i++)
    {
        int x=read(),y=read(),z=read(),c=read();
        xx[i]=x;yy[i]=y;zz[i]=z;
        if(getz(i)==0)c*=11;
        else c*=10;
        ans+=c;
        x-=z;y-=z;
        if(!node[mp(x,y)])node[mp(x,y)]=++gpc;
        vv[node[mp(x,y)]]+=c;
    }
    for(int i=1;i<=n;i++)
    {
        int x=xx[i]-zz[i],y=yy[i]-zz[i];
        int now=node[mp(x,y)];
        if(vis[now])continue;vis[now]=1;
        add(now,now+n,vv[now]);
        if(getz(i)==1)add(S,now,inf);
        else if(getz(i)==0)
        {
            int to;
            to=node[mp(x-1,y-1)];if(to)add(to+n,now,inf);
            to=node[mp(x,y+1)];  if(to)add(to+n,now,inf);
            to=node[mp(x+1,y)];  if(to)add(to+n,now,inf);
            to=node[mp(x+1,y+1)];if(to)add(now+n,to,inf);
            to=node[mp(x,y-1)];  if(to)add(now+n,to,inf);
            to=node[mp(x-1,y)];  if(to)add(now+n,to,inf);
        }else add(now+n,T,inf);
    }
    dinic();
    printf("%.1lf\n",0.1*ans);
}

T5 P2762 太空飞行计划问题

题目大意
\(m\)个实验和\(n\)种仪器。做第\(i\)个实验需要一系列仪器\(R_i∈I\)(假设\(I\)表示所有仪器的集合),可以得到\(P_i\)的报酬。第\(i\)个仪器采购需要花费\(C_i\)元。仪器可以重复使用。
求最大收益以及方案。
\(1≤n,m≤50\)
Sol
先把所有报酬加起来求得\(ans\)。然后按照如下方式找最小割:
源点向实验建边,流量\(P_i\)
实验向仪器建边,流量\(inf\)
仪器向汇点建边,流量\(C_i\)
这样建出来的图表示的意思就是:如果一个仪器满流了,那么就需要购买该仪器;如果一个实验满流了,那么就用不着做这项实验,因为它的成本比它的报酬还高。
方案就是\(Dinic\)最后一次搜索中那些剩下的广搜仍能到达的点所对应的实验和仪器。
code

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010,inf=2e9;
bool flag;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    if(c=='\r'||c=='\n')flag=1;
    return x*f;
}
struct edge
{
    int to,next,v;
}e[maxn<<1];
int h[maxn],ei=1;
inline void add(int x,int y,int v)
{
    e[++ei]=(edge){y,h[x],v};
    h[x]=ei;
    e[++ei]=(edge){x,h[y],0};
    h[y]=ei;
    return;
}
int n,m,S,T,ans,gpc;
int dep[maxn];
bool vis[maxn];
queue<int>qu;
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    dep[S]=1;qu.push(S);
    while(!qu.empty())
    {
        int x=qu.front();qu.pop();
        for(int i=h[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(dep[to]||e[i].v==0)continue;
            dep[to]=dep[x]+1;
            qu.push(to);
        }
    }
    return dep[T]>0;
}
inline int dfs(int x,int maxflow)
{
    if(x==T)return maxflow;
    int flow=0;
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(dep[to]!=dep[x]+1||e[i].v==0)continue;
        int rst=dfs(to,min(maxflow-flow,e[i].v));
        if(rst==0)dep[to]=0;
        e[i].v-=rst;e[i^1].v+=rst;
        flow+=rst;
        if(flow==maxflow)break;
    }
    return flow;
}
inline void dinic()
{
    while(bfs())ans-=dfs(S,inf);
}
int main()
{
    m=read();n=read();
    S=0;T=n+m+1;
    for(int i=1;i<=m;i++)
    {
        int x=read();flag=0;
        ans+=x;
        add(S,i,x);
        while(!flag)
        {
            x=read();add(i,x+m,inf);
        }
    }
    for(int i=1;i<=n;i++)
    {
        int x=read();add(i+m,T,x);
    }
    dinic();
    for(int i=1;i<=m;i++)if(dep[i])printf("%d ",i);printf("\n");
    for(int i=1;i<=n;i++)if(dep[i+m])printf("%d ",i);printf("\n");
    printf("%d\n",ans);
}

另外这个题的输入有点毒,读入的时候要注意读入方式。

总结

大致总结一下网络流的题的特点:核心代码更多的是在建边而不是网络流算法本身,因为大多数时候板子都是不变的,而很多时候这些建边都很巧妙,不那么容易想得出来,而事实上这些巧妙的建边方式本身又有很多固定套路和思想,所以还是只能通过多加练习,见到更多的模型才能更好的解决网络流题目。

posted @ 2021-09-10 20:31  wwlvv  阅读(56)  评论(0)    收藏  举报