【多题合集】网络流24题练习(更新至魔术球问题)

飞行员配对问题(仅求方案总数)

传送门

思路:二分图后dinic走起,各边容量为1

代码:

#include<bits/stdc++.h>
#define inf 0x7ff
using namespace std;
int ans,n,n1,s,t,tot=1;
int first[105],dis[105],up[105];
bool vis[105];
queue<int>q;
struct edge
{
    int u,v,next,w;
}e[500];
void add(int x,int y,int z)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    memset(up,0,sizeof(up));
    dis[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
            if (!dis[e[i].v]&&e[i].w)
                q.push(e[i].v),
                dis[e[i].v]=dis[k]+1;
    }
    return dis[t];
}
void flow()
{
    int minn=0x7ff;
    for (int i=up[t];i;i=up[e[i].u])
    minn=min(minn,e[i].w);
    ans+=minn;
    for (int i=up[t];i;i=up[e[i].u])
    e[i].w-=minn,e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
    if (x==t) {flow();return;}
    for (int i=first[x];i;i=e[i].next)
    if (e[i].w&&dis[e[i].v]==dis[x]+1)
    up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
    scanf("%d%d",&n,&n1);
    int x,y;
    s=n+1;t=n+2;
        while (scanf("%d%d",&x,&y)!=EOF)
        add(x,y,1),
        add(y,x,0),
        vis[x]=1;
    for (int i=1;i<=n;i++)
    if (vis[i]) add(s,i,1),add(i,s,0);
    else add(i,t,1),add(t,i,0);
    while (bfs())
    dfs(s,0x7ff);
    printf("%d",ans);
}

太空飞行计划

传送门

思路:(在codevs上调了好久,最终弃疗)

cogs上是不用输出收益为0方案,所以我们直接学习了最大权闭合子图的姿势,求出最大流,并用所有点的正权值减去它就可以了

代码:

#include<bits/stdc++.h>
#define inf 0x7fffff
using namespace std;
int ans,n,m,tot=1,s,t;
int first[205],dis[205],up[205];
bool mac[102],exps[102];
queue<int>q;
struct edge
{
    int u,v,w,next;
}e[40000];
void add(int x,int y,int z)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    memset(up,0,sizeof(up));
    q.push(s);
    dis[s]=1;
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
        if (e[i].w&&!dis[e[i].v])
            dis[e[i].v]=dis[k]+1,q.push(e[i].v);
    }
    return dis[t];
}
void flow()
{
    int minn=inf;
    for (int i=up[t];i;i=up[e[i].u])
        minn=min(minn,e[i].w);
    ans-=minn;
    for (int i=up[t];i;i=up[e[i].u])
        e[i].w-=minn,
        e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
    if (x==t) {flow();return;}
    for (int i=first[x];i;i=e[i].next)
    if (e[i].w&&dis[e[i].v]==dis[x]+1)
        up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
    scanf("%d%d",&m,&n);
    s=m+n+1;t=m+n+2;
    int x;
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&x);
        ans+=x;
        add(s,i+n,x);
        add(i+n,s,0);
        while (1)
        {
            scanf("%d",&x);
            add(i+n,x,inf);
            add(x,i+n,0);
            if (getchar()!=‘ ‘) break;
        }
    }
    for (int i=1;i<=n;i++)
        scanf("%d",&x),add(i,t,x),add(t,i,0);
    while (bfs()) dfs(s,inf);
    for (int i=first[s];i;i=e[i].next)
        if (dis[e[i].v]) exps[e[i].v-n]=1;
    for (int i=first[t];i;i=e[i].next)
        if (dis[e[i].v]) mac[e[i].v]=1;
    for (int i=1;i<=m;i++)
        if (exps[i]) printf("%d ",i);
    puts("");
    for (int i=1;i<=n;i++)
        if (mac[i]) printf("%d ",i);
    puts("");
    printf("%d",ans);
}

圆桌问题

传送门

思路:这个构图太简单……不说了

注意:

1.时间上的优化!我之前写的这些都是扯淡!

2.别上codevs评测,没有SPJ

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,tot=1,ans,sum;
int first[500],dis[500],num[500][500],up[500],cur[500],cnt[500];
queue<int>q;
struct edge
{
    int u,v,next,w;
}e[100000];
void add(int x,int y,int z)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    dis[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
            if (!dis[e[i].v]&&e[i].w) dis[e[i].v]=dis[k]+1,q.push(e[i].v);
    }
    if (dis[t])
        for (int i=1;i<=t;i++) cur[i]=first[i];
    return dis[t];
}
void flow()
{
    int minn=0x7fffff;
    for (int i=up[t];i;i=up[e[i].u]) minn=min(minn,e[i].w);
    if (!minn) return;
    ans+=minn;
    for (int i=up[t];i;i=up[e[i].u])
    {
        e[i].w-=minn;e[i^1].w+=minn;
        if (e[i].u<=m&&e[i].v<=n+m)
            num[e[i].u][e[i].v-m]^=1;
        else if (e[i].v<=m&&e[i].u<=n+m)
            num[e[i].v][e[i].u-m]^=1;
    }
}
int dfs(int x,int maxn)
{
    if (x==t) {flow();return maxn;}
    int used=0;
    for (int i=cur[x];i;i=e[i].next)
    if (dis[e[i].v]==dis[x]+1)
    {
        up[e[i].v]=i;
        int flow=dfs(e[i].v,min(maxn,e[i].w));
        used+=flow;
        if (e[i].w) cur[x]=i;
        if (used==maxn) return maxn;
    }
    return used;
}
main()
{
    scanf("%d%d",&m,&n);
    int x;
    s=n+m+1;t=n+m+2;
    for (int i=1;i<=m;i++)
        scanf("%d",cnt+i),
        sum+=cnt[i],
        add(s,i,cnt[i]),
        add(i,s,0);
    for (int i=1;i<=n;i++)
        scanf("%d",&x),
        add(i+m,t,x),
        add(t,i+m,0);
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++)
            add(i,j+m,1),
            add(j+m,i,0);
    while (bfs()) dfs(s,0x7ffff);
    if (ans<sum) {printf("0");return 0;}
    puts("1");
    for (int i=1;i<=m;i++)
    {
        for (int j=1;j<=n;j++)
        if (num[i][j]) printf("%d ",j);
        puts("");
    }
}

最长上升子序列问题

传送门

思路:发现网上大部分都是最大流拆点,而我写了个半残的费用流……

1.s->i连容量为1,费用为-1的边

2.i->t连容量为1,费用为0的边

3.i->j(a[i]<a[j])连容量为1,费用为-1的边

跑最小费用即可,算长度的话只用记录每次增广路上的费用总和就行了(不要乘流量,单算费用)

不用DP干掉1,2问

第3问把s->1,s->n,1->t,n->t的边容量无穷大就行了

注意:

求的是最长不下降子序列!不是最长上升子序列!这里坑爆了!

#include<bits/stdc++.h>
using namespace std;
int n,a[510],s,t,tot=1,len,ans=1;
int first[510],up[510],dis[510];
bool vis[510];
queue<int> q;
struct edge
{
    int u,v,w,next,cost;
}e[200000];
void add(int x,int y,int z,int c)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].cost=c;
    e[tot].next=first[x];
    first[x]=tot;
}
bool spfa()
{
    memset(dis,63,sizeof(dis));
    memset(up,0,sizeof(up));
    dis[s]=0;vis[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        vis[k]=0;
        for (int i=first[k];i;i=e[i].next)
        if (e[i].w&&dis[e[i].v]>dis[k]+e[i].cost)
        {
            dis[e[i].v]=dis[k]+e[i].cost;
            up[e[i].v]=i;
            if (!vis[e[i].v]) vis[e[i].v]=1,q.push(e[i].v);
        }
    }
    return dis[t]<0x7ffff;
}
int flow()
{
    int p=0,minn=0x7fffff;
    for (int i=up[t];i;i=up[e[i].u])
        minn=min(e[i].w,minn),
        p+=e[i].cost;
    for (int i=up[t];i;i=up[e[i].u])
        e[i].w-=minn,
        e[i^1].w+=minn;
    return p;
}
main()
{
    scanf("%d",&n);
    s=n+1;t=n+2;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        add(s,i,1,-1);
        add(i,s,0,1);
        add(i,t,1,0);
        add(t,i,0,0);
        for (int j=1;j<i;j++)
            if (a[j]<=a[i])
                add(j,i,1,-1),
                add(i,j,0,1);
    }
    for (int i=tot+1;i<=tot*2;i++) e[i]=e[i-tot];
    while (spfa())
    {
        if (!len) len=flow();
        else if (len!=flow()) break;
        else ans++;
    }
    printf("%d\n%d\n",-len,ans);
    for (int i=1;i<=tot;i++)
    {
        e[i]=e[i+tot];
        if ((e[i].u==s&&e[i].v==n)||(e[i].u==s&&e[i].v==1)) e[i].w=0x7ff;
        if ((e[i].u==n&&e[i].v==t)||(e[i].u==1&&e[i].v==t)) e[i].w=0x7ff;
    }
    ans=1;len=0;
    while (spfa())
    {
        if (!len) len=flow();
        else if (len!=flow()) break;
        else ans++;
    }
    printf("%d",ans);
}

最小路径覆盖问题

传送门

思路:

二分图最大流,答案=点数-流量和

方案数从1-n枚举dfs找路径即可,要求流量为0

注意:输出格式注意!最后要换行!codevs上只用输出方案数!

#include<bits/stdc++.h>
using namespace std;
int n,m,tot=1,s,t;
int first[320],dis[320],cur[320],ans;
int num[320];
bool vis[320];
queue<int>q;
struct edge
{
    int u,v,w,next;
}e[15000];
void add(int x,int y,int z)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    dis[s]=1;
    q.push(s);
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
        if (e[i].w&&!dis[e[i].v])
            dis[e[i].v]=dis[k]+1,
            q.push(e[i].v);
    }
    if (dis[t])
        for (int i=1;i<=t;i++) cur[i]=first[i];
    return dis[t];
}
int dfs(int x,int maxn)
{
    if (x==t) {return maxn;}
    int used=0;
    for (int i=cur[x];i;i=e[i].next)
    if (dis[e[i].v]==dis[x]+1)
    {
        int k=dfs(e[i].v,min(maxn,e[i].w));
        e[i].w-=k;e[i^1].w+=k;
        used+=k;
        if (e[i].w) cur[x]=i;
        if (used==maxn) return maxn;
    }
    if (!used) dis[x]=0;
    return used;
}
void find(int x)
{
    num[++num[0]]=x;
    vis[x]=1;
    for (int i=first[x];i;i=e[i].next)
    if (!e[i].w&&e[i].v!=s&&!vis[e[i].v-n]) find(e[i].v-n);
}
main()
{
    freopen("path3.in","r",stdin);
    freopen("path3.out","w",stdout);
    scanf("%d%d",&n,&m);
    int x,y;
    for (int i=1;i<=m;i++)
        scanf("%d%d",&x,&y),
        add(x,y+n,1),
        add(y+n,x,0);
    s=(n<<1)+1;t=n+1<<1;
    for (int i=1;i<=n;i++)
        add(s,i,1),
        add(i,s,0),
        add(i+n,t,1),
        add(t,i+n,0);
    while (bfs())
    ans+=dfs(s,0x7ff);
    for (int i=1;i<=n;i++)
        if (!vis[i])
        {
            num[0]=0,find(i);
            for (int i=1;i<=num[0];i++)
            printf("%d%c",num[i]," \n"[i==num[0]]);
        }
    printf("%d\n",n-ans);
}

餐巾计划问题

传送门1

传送门2

思路:

感觉好厉害,一开始没有考虑流量守恒,建图完全错了,该连源点的连汇点,该连汇点的连了源点(s连新餐巾,t连旧餐巾)然后就一直买啊买也不洗……改一下就连买都不买了……后来看了高大哥的课件建图,感觉自己还是功力不深啊!!

代码:

#include<bits/stdc++.h>
#define inf 0x7ffff
using namespace std;
int n,f,c1,c2,t1,t2,s,t,tot=1,ans;
bool vis[2010];
int dis[2010],first[2010],up[2010];
queue<int>q;
struct edge
{
    int u,v,w,cost,next;
}e[200000];
void add(int x,int y,int z,int c)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].cost=c;
    e[tot].next=first[x];
    first[x]=tot;
}
bool spfa()
{
    memset(dis,63,sizeof(dis));
    memset(up,0,sizeof(up));
    q.push(s);
    dis[s]=0;vis[s]=1;
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        vis[k]=0;
        for (int i=first[k];i;i=e[i].next)
            if (e[i].w&&dis[e[i].v]>dis[k]+e[i].cost)
            {
                dis[e[i].v]=dis[k]+e[i].cost;
                up[e[i].v]=i;
                if (!vis[e[i].v]) vis[e[i].v]=1,q.push(e[i].v);
            }
    }
    return dis[t]<inf;
}
void flow()
{
    int minn=inf;
    for (int i=up[t];i;i=up[e[i].u]) minn=min(e[i].w,minn);
    for (int i=up[t];i;i=up[e[i].u])
        e[i].w-=minn,
        e[i^1].w+=minn,
        ans+=minn*e[i].cost;
}
main()
{
//  scanf("%d%d%d%d%d%d",&n,&f,&t1,&c1,&t2,&c2);codevs上的

//  scanf("%d%d%d%d%d%d",&n,&t1,&t2,&f,&c1,&c2);
//  t1++;t2++;BZOJ上的
    s=(n<<1)+1;t=1+n<<1;
    int x;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        add(i+n,t,x,0);
        add(t,i+n,0,0);
        add(s,i,x,0);
        add(i,s,0,0);
        add(s,i+n,inf,f);
        add(i+n,s,0,-f);
        if (i+1<=n) add(i,i+1,inf,0),add(i+1,i,0,0);
        if (i+t1<=n) add(i,i+t1+n,inf,c1),add(i+t1+n,i,0,-c1);
        if (i+t2<=n) add(i,i+t2+n,inf,c2),add(i+t2+n,i,0,-c2);
    }
    while (spfa()) flow();
    printf("%d",ans);
}

星际转移问题(家园)

传送门1

传送门2

思路:数据比较小,可以枚举时间动态开点连边,把图分层,各个太空站在每一个时刻都是一个独立的点,样例中的

2 2 1

1 3 0 1 2

1 3 1 2 –1

构图为这样(没有连边的原因是画出来就很乱,所以不连了)

如果我们把时刻为t时的i点标号为st[t][i]且在这个飞船的下一站为next[i],我们要连的就是(st[t][i],st[t+1][next[i]])

同时,每个太空站是可以留人的,所以还要连(st[t][i],st[t+1][i]

然后枚举时间由1->+∞跑最大流,当总流量>=t时就可以停下了

注意:

1.我们不可能一直把时间枚举下去,差不多t=500(可能更小)的时候就可以停下输出无解了

2.给点标号着实让我蛋疼了好久,大家自己感受下吧

#include<bits/stdc++.h>
#define inf 0x7fff
using namespace std;
int ans,times,n,m,k,tot=1,s,t;
int cur[5000],dis[5000],first[5000],p[30],st[30][17],num[30];
queue<int>q;
struct edge
{
    int u,v,w,next;
}e[20000];
void add(int x,int y,int z)
{
    e[++tot].u=x;
    e[tot].v=y;
    e[tot].w=z;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    q.push(s);dis[s]=1;
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
        if (!dis[e[i].v]&&e[i].w)
            dis[e[i].v]=dis[k]+1,q.push(e[i].v);
    }
    cur[s]=first[s];cur[t]=first[t];
    if (dis[t]) for (int i=1;i<=times*m;i++) cur[i]=first[i];
    return dis[t];
}
int dfs(int x,int maxn)
{
    if (x==t) return maxn;
    int used=0;
    for (int i=cur[x];i;i=e[i].next)
    if (dis[e[i].v]==dis[x]+1)
    {
        int k=dfs(e[i].v,min(maxn,e[i].w));
        used+=k;
        e[i].w-=k;
        e[i^1].w+=k;
        if(e[i].w) cur[x]=i;
        if (used==maxn) return maxn;
    }if (!used) dis[x]=0;
    return used;
}
main()
{
    scanf("%d%d%d",&n,&m,&k);
    s=4999;t=4998;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",p+i,num+i);
        for (int j=0;j<num[i];j++)
        {
            scanf("%d",st[i]+j);
            if (st[i][j]<=0) st[i][j]+=4999;
        }
    }
    while (++times<=500)
    {
        for (int i=1;i<=m;i++)
        {
            int x,y;
            if (st[i][(times-1)%num[i]]==s) x=s;
            else if (st[i][(times-1)%num[i]]==t) x=t;
            else x=st[i][(times-1)%num[i]]+n*(times-1);
            if (st[i][times%num[i]]==s) y=s;
            else if (st[i][times%num[i]]==t) y=t;
            else y=st[i][times%num[i]]+n*times;
            add(x,y,p[i]);
            add(y,x,0);
        }
        for (int i=n*(times-1)+1;i<=n*(times-1)+n;i++)
            add(i,i+n,inf),add(i+n,i,0);
        while (bfs()) ans+=dfs(s,0x7ff);
        if (ans>=k) printf("%d",times),exit(0);
    }
    printf("0");
}

骑士共存问题

传送门

思路:根据所在行列之和x+y的奇偶来进行二分图,然后跑dinic

s->奇 容量为1

偶数->t 容量为1

奇数->被他影响的8个点 容量为inf

最后得出的最大流就是放完最多数后的不能放的点(不包括m个点),答案就是n*n-m-maxflow

代码:

#include<bits/stdc++.h>
#define inf 0x7ffff
#define pd(x,y,z) (x<=y&&y<=z)
using namespace std;
int ans,s,t,n,m,x,y,tot=1;
int dis[80010],first[80010],cur[80010];
int dx[9]={0,-1,-2,-2,-1,1,2,2,1},dy[9]={0,-2,-1,1,2,-2,-1,1,2};
bool vis[80010];
queue<int>q;
struct edge
{
    int u,v,w,next;
}e[410000];
void add(int x,int y,int z)
{
    e[++tot]=(edge){x,y,z,first[x]};
    first[x]=tot;
}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    q.push(s);dis[s]=1;
    while (!q.empty())
    {
        int k=q.front();
        q.pop();
        for (int i=first[k];i;i=e[i].next)
            if (e[i].w&&!dis[e[i].v])
                q.push(e[i].v),dis[e[i].v]=dis[k]+1;
    }
    if (dis[t])
        for (int i=1;i<=t;i++) cur[i]=first[i];
    return dis[t];
}
int dfs(int x,int maxn)
{
    if (x==t) return maxn;
    int used=0;
    for (int i=cur[x];i;i=e[i].next)
    if (dis[e[i].v]==dis[x]+1)
    {
        int k=dfs(e[i].v,min(maxn,e[i].w));
        e[i].w-=k;
        e[i^1].w+=k;
        if (e[i].w) cur[x]=i;
        used+=k;
        if (used==maxn) return maxn;
    }
    if (!used) dis[x]=0;
    return used;
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf("%d%d",&x,&y),
        vis[(x-1)*n+y]=1;
    s=n*n+1;t=n*n+2;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        if (!vis[(i-1)*n+j])
        {
            x=(i-1)*n+j;
            if ((i+j)&1)
            {
                add(s,x,1);add(x,s,0);
                for (int k=1;k<=8;k++)
                if (pd(1,i+dx[k],n)&&pd(1,j+dy[k],n))
                    add(x,n*(i+dx[k]-1)+j+dy[k],inf),
                    add(n*(i+dx[k]-1)+j+dy[k],x,0);
            }
            else add(x,t,1),add(t,x,0);
        }
    while (bfs())
        ans+=dfs(s,inf);
    printf("%d",n*n-ans-m);
}

魔术球问题(简化版)

传送门

思路:枚举答案,动态加点判断,但一开始我是暴力加边,多建了一层关于“柱子”的点,只过了9个点,后来看到黄学长的blog才幡然醒悟,合并点,这里直接求最小路径覆盖就好了,每次加入一个待放的球,跑最大流,判断一下,如果它不能归到之前的路径上就让ans++,直到ans>n

注意:codevs上没有SPJ,但要输出方案数!

#include<bits/stdc++.h>
using namespace std;
int n,s,t,tot=1,ans=1;
struct edge
{
    int u,v,w,next;
}e[200000];
int first[3500],dis[3500];
bool sr[3204];
queue<int>q;
void add(int x,int y,int z){e[++tot]=(edge){x,y,z,first[x]};first[x]=tot;}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    q.push(s);dis[s]=1;
    while (!q.empty())
    {
        int k=q.front();
        for (int i=first[k];i;i=e[i].next)
        if (e[i].w&&!dis[e[i].v])
            q.push(e[i].v),dis[e[i].v]=dis[k]+1;
        q.pop();
    }
    return dis[t];
}
int dfs(int x,int maxn)
{
    if (x==t) return maxn;
    int used=0;
    for (int i=first[x];i;i=e[i].next)
        if (dis[e[i].v]==dis[x]+1)
        {
            int k=dfs(e[i].v,min(maxn,e[i].w));
            e[i].w-=k;e[i^1].w+=k;
            used+=k;
            if (used==maxn) return maxn;
        }
    if (!used) dis[x]=0;
    return used;
}
main()
{
    freopen("balla.in","r",stdin);
    freopen("balla.out","w",stdout);
    for (int i=1;i<=56;i++) sr[i*i]=1;
    t=3244;
    scanf("%d",&n);
    for (int i=1;i<=1600;i++,ans++)
    {
        add(s,i,1);add(i,s,0);
        for (int j=1;j<i;j++)
            if (sr[j+i]) add(j,i+1600,1),add(i+1600,j,0);
        add(i+1600,t,1);
        add(t,i+1600,0);
        while (bfs()) ans-=dfs(s,0x7f);
        if (ans>n) {ans=i;break;}
    }
    printf("%d",ans-1);
}

方格取数问题

传送门

思路:二分图染黑白,然后就是求最大权闭合子图了,可以参照上面的太空飞行计划,流量就是点的权值

不过这道题好坑啊,之前的最大流方法总会WA三个点,只能用回最暴力的dfs单线跑流了

代码:

#include<bits/stdc++.h>
#define inf 0x7fffff
#define pd(x,y,z) (x<=y&&y<=z)
using namespace std;
int n,m,s,t,tot=1,sum,ans;
int dis[1000],first[1000],cur[1000],up[1000];
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
queue<int>q;
struct edge
{
    int u,v,w,next;
}e[10000];
void add(int x,int y,int z) {e[++tot]=(edge){x,y,z,first[x]};first[x]=tot;}
bool bfs()
{
    memset(dis,0,sizeof(dis));
    dis[s]=1;q.push(s);
    while (!q.empty())
    {
        int k=q.front();
        for (int i=first[k];i;i=e[i].next)
            if (e[i].w&&!dis[e[i].v])
                q.push(e[i].v),dis[e[i].v]=dis[k]+1;
        q.pop();
    }
    if (dis[t])
        for (int i=s;i<=t;i++) cur[i]=first[i];
    return dis[t];
}
void flow()
{
    int minn=inf;
    for (int i=up[t];i;i=up[e[i].u])
    minn=min(minn,e[i].w);
    ans+=minn;
    for (int i=up[t];i;i=up[e[i].u])
    e[i].w-=minn,e[i^1].w+=minn;
}
void dfs(int x,int maxn)
{
    if (x==t) {flow();return;}
    for (int i=first[x];i;i=e[i].next)
    if (e[i].w&&dis[e[i].v]==dis[x]+1)
    up[e[i].v]=i,dfs(e[i].v,min(maxn,e[i].w));
}
main()
{
    scanf("%d%d",&n,&m);
    int x;
    t=n*m+1;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            scanf("%d",&x);
            sum+=x;
            if (((i+j)&1))
            {
                add(s,m*(i-1)+j,x);
                add(m*(i-1)+j,s,0);
                for (int k=0;k<4;k++)
                    if(pd(1,i+dx[k],n)&&pd(1,j+dy[k],m))
                        add(m*(i-1)+j,m*(i-1+dx[k])+j+dy[k],inf),
                        add(m*(i-1+dx[k])+j+dy[k],m*(i-1)+j,0);
            }
            else add(m*(i-1)+j,t,x),add(t,m*(i-1)+j,0);
        }
    while (bfs()) dfs(s,inf);
    printf("%d",sum-ans);
}
时间: 2024-11-07 17:05:14

【多题合集】网络流24题练习(更新至魔术球问题)的相关文章

线性规划与网络流24题 索引

线性规划与网络流24题 可参考 网络流24题分类

题解:线性规划与网络流24题 T2 太空飞行计划问题

太空飞行计划问题 问题描述 W教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,-,Em},和进行这些实验需要使用的全部仪器的集合I={I1,I2,-In}.实验Ej 需要用到的仪器是I的子集Rj ∈ I.配置仪器Ik的费用为ck美元.实验Ej 的赞助商已同意为该实验结果支付pj 美元.W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大.这里净收

网络流24题刷题记录

题目一:飞行员配对方案问题 一群飞行员和另一群飞行员之间有的可以配对,有的不能配对,求最多可以配多少对? 典型二分图最大匹配问题.可以用匈牙利算法 或者 加上源点汇点之后跑最大流.[感觉第二个打错的概率还低一些]. [还是介绍一下匈牙利算法吧][看白书大法好!] 从左边(s集)一个未盖点出发(还有没有和任何人匹配的点)出发,顺次经过未选边->选边->未选边.....[这样的路叫做交替路] 如果路径当中经过一个未盖点[这样的交替路叫增广路]...那么将所有的选边变成不选,不选的边选上,就可以多一

线性规划与网络流24题第2题 太空飞行计划 最小割

/** 题目: 线性规划与网络流24题第2题 太空飞行计划 最小割 链接:http://www.cogs.pro/cogs/problem/problem.php?pid=727 题意:lv 思路:最大点权独立集(点集中任意两个点没有边相连,且点权和最大)=点权总和-最小点权覆盖集. 将实验和仪器看做节点. 实验放在二分图的左边, s->x, cap = 实验利润. 仪器放在右边, x->t, cap = 仪器费用. 如果实验u的进行需要仪器v,u->v, cap = INF. ans

734. [网络流24题] 方格取数问题 二分图点权最大独立集/最小割/最大流

?问题描述:在一个有m*n 个方格的棋盘中,每个方格中有一个正整数.现要从方格中取数,使任意2 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法.?编程任务:对于给定的方格棋盘,按照取数要求编程找出总和最大的数.?数据输入:由文件grid.in提供输入数据.文件第1 行有2 个正整数m和n,分别表示棋盘的行数和列数.接下来的m行,每行有n个正整数,表示棋盘方格中的数. [问题分析] 二分图点权最大独立集,转化为最小割模型,从而用最大流解决. [建模方法] 首先把棋盘黑白

LiberOJ #6007. 「网络流 24 题」方格取数 最小割 最大点权独立集 最大流

#6007. 「网络流 24 题」方格取数 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 在一个有 m×n m \times nm×n 个方格的棋盘中,每个方格中有一个正整数. 现要从方格中取数,使任意 2 22 个数所在方格没有公共边,且取出的数的总和最大.试设计一个满足要求的取数算法. 输入格式 文件第 1 11 行有 2 22 个正整数 m mm 和 n nn,分别表示棋盘的行数和列数

「网络流24题」2. 太空飞行计划问题

「网络流24题」2. 太空飞行计划问题 <题目链接> 最大权闭合子图. 源点与实验连边权为实验费用的有向边: 仪器与汇点连边权为仪器费用的有向边: 实验与仪器之间连边权为INF的有向边. 答案为所有与源点相连的边的边权和减去图的最小割. 证明见国集队员胡伯涛论文<最小割模型在信息学竞赛中的应用>. 输出路径时,最后一次层次图中: 与源点相连的点即选做的实验:与汇点相连的点即选用的仪器. 注意 ·读入数据时,读到空格继续,否则停止. ·仪器部分的点权+50,避免两部点权相同. #in

「网络流24题」 9. 方格取数问题

「网络流24题」 9. 方格取数问题 <题目链接> 二分图的最大点权独立集 建立二分图,使得每个点与其相邻的点在不同的部. 源向X部引有向边,Y部向汇引有向边,边权为点权. X部每个点到其相邻的点引有向边,边权INF,这个边的两个断电不能同时被选. 那么S-X-Y-T的任意一条增广路都表示选了两个相邻的点. 于是问题转化为求网络最小割. 最终的答案为所有点的点权和(先都选上)减去网络最小割(不能选的最小点权集). #include <algorithm> #include <

「网络流24题」 题目列表

「网络流24题」 题目列表 序号 题目标题 模型 题解 1 飞行员配对方案问题 二分图最大匹配 <1> 2 太空飞行计划问题 最大权闭合子图 <2> 3 最小路径覆盖问题 二分图最小路径覆盖 <3> 4 魔术球问题 <4> 5 圆桌问题 <5> 6 最长递增子序列问题 <6> 7 试题库问题 <7> 8 机器人路径规划问题 <8> 9 方格取数问题 二分图最大点权独立集 <9> 10 餐巾计划问题

网络流24题 部分总结

网络流24题 部分总结 慢慢写吧... 以前做过一些了: 然后发现也做了不少了,集中写一下. 警告: 题目按照随机顺序排列. 文章中只有建模的方法. 最小路径覆盖问题 http://cogs.pro:8080/cogs/problem/problem.php?pid=728 题目即题解... // It is made by XZZ #include<cstdio> #include<algorithm> #include<cstring> #define File #