网络流24题(持续更新)和一些好题

记录下网络流的学习,以及自己的一些理解

1.DAG最小路径覆盖

题目大意:DAG求最小路径覆盖,输出方案

思路:将n个点看成单独n条路径,接下来拆点,将每个点放在入点出点两个集合中,如果有边x->y,则x出指向y入,初始化时候所有点出点连源点,入点连汇点,最小路径数 = 总路径n - 最大流,最大流就是我们去匹配出入点的最大边数,实质就是匈牙利算法啊

建图示意 ,图中只有1->3一条路径可以从原始n条路径中拼接起来,即3-1=2,最小路径数,我们发现,连向汇点的点如果不和源点联通(点1,2),说明它会作为一条新路径的起点,即要新开一条路给它,因为输出方案时候要用,也就是说它连向汇点的边的流量此时为1(因为它未用过啊),edge[T^1].flow==1

注意点:(可能对于我来说),链式前向星从0或者2开始存图,因为要存反边,平时都从1开始存的,反边变成0了,调了两天才看出来

———————————————————————————————————————————————————————

回头又思烤了虾,说是拆成出点和入点有问题,就是进行边的匹配,尽可能的选多的边,才能使得覆盖路径最少,说拆点反而有误导倾向了 = = ||

如果拆点做的话,按照正常逻辑建图,点i的出入点连边,而每个点都可以作为路径的起点和终点,那最大流肯定为n了

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
//#define int long long
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=1e4+10;
int n,np,nc,m;
int s,t;
int d[N];
int pre[N];
int nxt[N];
struct ty
{
    int t,next,flow;
}edge[5010];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
    edge[++tot]={y,head[x],flow};
    head[x]=tot;
    edge[++tot]={x,head[y],0};
    head[y]=tot;
}
void ini()
{
    tot=1;
    mst(head,-1);
}
bool bfs(int t)
{
    mst(d,0);
    queue <int> q;
    q.push(0);
    d[0]=1;
    while( !q.empty() )
    {
        int x = q.front();
        q.pop();
        if( x==t ) return true;
        for(int i=head[x] ;i!=-1;i=edge[i].next)
        {
            int y = edge[i].t;
            if( edge[i].flow > 0 && !d[y] )
            {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return false;
}
int dfs(int x ,int flow ,int t)
{
    if( x==t ) return flow;
    int sum=0;
    for(int i=head[x] ;i!=-1;i=edge[i].next)
    {
        int y = edge[i].t;
        if( d[y] == d[x] +1 && edge[i].flow>0 )
        {
            int temp = dfs(y,min(edge[i].flow , flow-sum),t);
            if( temp )//有流就记录路径
            {
                nxt[x] =y-n;//记录下一步
            }
            edge[i].flow -= temp;
            edge[i^1].flow += temp;
            sum += temp;
            if( sum == flow ) return flow;
        }
    }
    return sum;
}
signed main()
{
    ///!!!
//    freopen("data.txt","r",stdin);
    //!!!=
    IOS;
    ini();
    cin>>n>>m;
    s=0,t=2*n+1;
    //实质是二分图匹配
    _for(i,1,n)
    {
        add(0,i,1);add(i+n,2*n+1,1);
    }
    _for(i,1,m)
    {
        int x,y;
        cin>>x>>y;
        add(x,y+n,1);
    }
    int ans=0;
    while( bfs(t) ) ans += dfs(s,inf,t);
    for(int i=head[t] ;i!=-1 ;i=edge[i].next)
    {
        if( edge[i^1].flow ==1) //如果此点连到汇点
        //说明是所在路径的起点,注意对于汇点来说它是反边
        {
            int y = edge[i].t - n;
            while( y )
            {
                cout<<y<<" ";
                y = nxt[y];
            }
            cout<<endl;
        }
    }
    cout<<n-ans<<endl;
}

2 魔术球

题目大意:n个柱子,每个柱子上放数字,相邻数字的和必须为平方数,求n个柱子最多放几个数字

思路:既然数字的接龙有限制,若x+y为平方数,我们就对x,y连边,每个柱子肯定放的数字越多越好,柱子数也要尽可能的少,那不就转化为上一题dag最小路径覆盖了吗?

柱子数就是路径数量,建图和上面几乎一样

不过这里路径条数规定好了,所以我们不断往图里加点,如果路径条数仍小于n就继续加点,否则break。简单说就加一个点,跑一下网络流,直到路径数多于n。

注意:要顺序输出柱子的方案,而且卡格式,不能输出多余空格 = = ||

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=2e5+10;
const int p = 100000;
int n,np,nc,m;
int s,t;
int d[N];
int pre[N];
int nxt[N];
int num[N],cnt;
struct ty
{
    int t,next,flow;
}edge[N];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
    edge[++tot]={y,head[x],flow};
    head[x]=tot;
    edge[++tot]={x,head[y],0};
    head[y]=tot;
}
void ini()
{
    tot=1;
    mst(head,-1);
}
bool bfs(int t)
{
    mst(d,0);
    queue <int> q;
    q.push(s);
    d[s]=1;
    while( !q.empty() )
    {
        int x = q.front();
        q.pop();
        if( x==t ) return true;
        for(int i=head[x] ;i!=-1;i=edge[i].next)
        {
            int y = edge[i].t;
            if( edge[i].flow > 0 && !d[y] )
            {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return false;
}
int dfs(int x ,int flow ,int t)
{
    if( x==t ) return flow;
    int sum=0;
    for(int i=head[x] ;i!=-1;i=edge[i].next)
    {
        int y = edge[i].t;
        if( d[y] == d[x] +1 && edge[i].flow>0 )
        {
            int temp = dfs(y,min(edge[i].flow , flow-sum),t);
            if( temp )//有流就记录路径
            {
                nxt[x] =y-p;//记录下一步
            }
            edge[i].flow -= temp;
            edge[i^1].flow += temp;
            sum += temp;
            if( sum == flow ) return flow;
        }
    }
    return sum;
}
bool check(int x ,int y)
{
    int temp=x+y;
//    cout<<" temp "<<temp<<" "<<(int) sqrt(temp)*(int) sqrt(temp)<<endl;
    return (int) sqrt(temp)*(int) sqrt(temp)  == temp;
}
signed main()
{
    ///!!!
//    freopen("data.txt","r",stdin);
    //!!!=
//    IOS;
    ini();
    cin>>n;
    s=0,t=2e5+1;
    int ans=0;
    for(int i=1;;i++)//枚举加入的数
    {
        add(s,i,1);add(i+p,t,1);
        _rep(j,i-1,1)
        {
//            cout<<"  i j "<<i<<" "<<j<<endl;
            if( check(i,j) )
            {
//                cout<<" ok "<<j<<"-> "<<i<<endl;
                add(j,i+p,1);
                continue;
            }

        }
        while( bfs(t) ) ans += dfs(s,inf,t);
        if( i - ans > n )//柱子放不下了
        {
            cout<<i-1<<endl;
            for(int j=head[t] ;j!=-1 ;j=edge[j].next)
            {
                if( edge[j^1].flow==1 && edge[j].t-p!=i )//如果没到汇点
                //说明是路径起点
                {
                    int y =  edge[j].t-p;
                    num[++cnt] = y;

                }
            }
            break;
        }
    }
    sort(num+1,num+1+cnt);//对柱子起点排序
    _for(i,1,cnt)
    {
        int y = num[i];
        while(y)
        {
            if(nxt[y]!=0 )cout<<y<<" ";
            else cout<<y;
            y = nxt[y];
        }
        cout<<endl;
    }
}

3最长递增子序列问题

n<=500

思路:(1)入门dp略了

(2)拆点——出点入点,跑完问题1的dp后,我们获得dp[i],以第i个数为结尾的最长上升子序列的长度。

如果求多条的话,就可以用网络流了。容量为1——每个数字只能用一次。两个数字a[i],a[j]要连边的话要符合dp[i] == dp[j] + 1&&a[i]>=a[j],因为要组成最多数的最长上升子序列的话,至少要遵照问题1跑出来的dp值相邻才能进行连边,然后dp值为1的连源点,dp值最大的连汇点,起点终点的选择应该挺好理解的。

(3)x1xn次数无限制,那就给1和n的出入点之间加容量为无穷的边,1和源点连inf的边,n和汇点的连边需要判断一下dp[n]是否为最大值(注意点),dp[1]因为肯定最小所以肯定为起点,dp[n]则不一定

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
//#define int long long
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=1e4+10;
const int p = 100000;
int s,t,n;
int d[N];
int a[N],dp[N];
int nxt[N];
int ans1;
struct ty
{
    int t,next,flow;
}edge[N];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
    edge[++tot]={y,head[x],flow};
    head[x]=tot;
    edge[++tot]={x,head[y],0};
    head[y]=tot;
}
void ini()
{
    tot=1;
    mst(head,-1);
}
bool bfs(int t)
{
    mst(d,0);
    queue <int> q;
    q.push(s);
    d[s]=1;
    while( !q.empty() )
    {
        int x = q.front();
        q.pop();
        if( x==t ) return true;
        for(int i=head[x] ;i!=-1;i=edge[i].next)
        {
            int y = edge[i].t;
            if( edge[i].flow > 0 && !d[y] )
            {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return false;
}
int dfs(int x ,int flow ,int t)
{
    if( x==t ) return flow;
    int sum=0;
    for(int i=head[x] ;i!=-1;i=edge[i].next)
    {
        int y = edge[i].t;
        if( d[y] == d[x] +1 && edge[i].flow>0 )
        {
            int temp = dfs(y,min(edge[i].flow , flow-sum),t);
            edge[i].flow -= temp;
            edge[i^1].flow += temp;
            sum += temp;
            if( sum == flow ) return flow;
        }
    }
    return sum;
}
void solve()
{
    //拆点
    s=0,t=2*n+1;
    _for(i,1,n)
    {
        if( dp[i] == 1 ) add(s,i,1);//dp值为1,向起点连边
        if( dp[i] == ans1 ) add(i+n,t,1);//dp值为max,要作为路径终点,向汇点连边
        _for(j,i+1,n)
        {
            if( dp[i] +1 == dp[j] && a[j]>=a[i] )//不仅要dp值相邻且a[i]值也要满足不下降
            {
                add(i+n,j,1);
            }
        }
        add(i,i+n,1);//入点向出点连边
    }
    int ans=0;
    while( bfs(t) ) ans += dfs(s,inf,t);-----
    cout<<ans<<endl;
    add(1,1+n,inf);add(s,1,inf);
    add(n,n+n,inf);
    if( dp[n] == ans1)
    add(n+n,t,inf);
    while( bfs(t) ) ans += dfs(s,inf,t);
    cout<<ans<<endl;

}
signed main()
{
    ///!!!
//    freopen("data.txt","r",stdin);
    //!!!=
    IOS;
    ini();
    cin>>n;_for(i,1,n) cin>>a[i];
    _for(i,1,n) dp[i]=1;
    _for(i,2,n)
    {
        _for(j,1,i-1)
        {
            if( a[i] >= a[j]  ) dp[i] = max(dp[j]+1,dp[i]);
        }
        ans1 = max(ans1,dp[i]);
    }
    cout<<ans1<<endl;
    solve();
}

4 方格取数

n,m<=100;

思路:之前训练赛做过一题数据范围是n,m<=20,当时是状压做的,现在压不了了 

写下最小割做法。

明显这是张二分图(相邻数字不能同时取),我们将图染成黑白后,假定所有数全都取,然后存在互相限制的点相互连边,表示它们直接存在矛盾关系,不能共存,问题就转化成删去最小边权和的边使得源点到汇点没有流量。

 如图所示,源点(汇点)到点之间的边权为方格内的值

又由最小割=最大流定理,答案即为sum - 最大流

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <map>
#include <vector>
#include <queue>
using namespace std;
#define _for(i,a,b) for(int i=(a) ;i<=(b) ;i++)
#define _rep(i,a,b) for(int i=(a) ;i>=(b) ;i--)
#define mst(v,s) memset(v,s,sizeof(v))
#define pb push_back
#define IOS ios::sync_with_stdio(false)
#define int long long
#define inf 0x3f3f3f3f
typedef long long ll;
const int N=1e3+10;
int n,np,nc,m;
int s,t;
int d[N];
int mp[N][N];
struct ty
{
    int t,next,flow;
}edge[2*6010];
int tot=1,head[N];
void add(int x ,int y, int flow)
{
    edge[++tot]={y,head[x],flow};
    head[x]=tot;
    edge[++tot]={x,head[y],0};
    head[y]=tot;
}
void ini()
{
    tot=1;
    mst(head,-1);
    cin>>n>>m;
    _for(i,1,n) _for(j,1,m) cin>>mp[i][j];
}
bool bfs(int t)
{
    mst(d,0);
    queue <int> q;
    q.push(0);
    d[0]=1;
    while( !q.empty() )
    {
        int x = q.front();
        q.pop();
//        cout<<" "<<x<<endl;
        if( x==t ) return true;
        for(int i=head[x] ;i!=-1;i=edge[i].next)
        {
            int y = edge[i].t;
            if( edge[i].flow > 0 && !d[y] )
            {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return false;
}
int dfs(int x ,int flow ,int t)
{
    if( x==t ) return flow;
    int sum=0;
    for(int i=head[x] ;i!=-1;i=edge[i].next)
    {
        int y = edge[i].t;
        if( d[y] == d[x] +1 && edge[i].flow>0 )
        {
            int temp = dfs(y,min(edge[i].flow , flow-sum),t);
            edge[i].flow -= temp;
            edge[i^1].flow += temp;
            sum += temp;
            if( sum == flow ) return flow;
        }
    }
    return sum;
}
signed main()
{
    ///!!!
//    freopen("data.txt","r",stdin);
    //!!!=
    IOS;
    ini();
    s=0,t=n*m+1;
    int sum=0;
    _for(i,1,n)
    {
        _for(j,1,m)
        {
            sum += mp[i][j];
            int num = (i-1)*m+j;
            if( (i+j)%2==0 )
            {
                add(s,num,mp[i][j]);
                if( j<m ) add(num,num+1,inf);
                if( j>1 ) add(num,num-1,inf);
                if( i<n ) add(num,num+m,inf);
                if( i>1 ) add(num,num-m,inf);
            }
            else
            {
                add(num,t,mp[i][j]);
            }
        }
    }
    int ans=0;
    while( bfs(t) ) ans += dfs(s,inf,t);
    cout<<sum-ans;
}

5.圆桌问题

鸽了一周多了,速速补上。。

 思路1:网络流,挺好想的,两个集合,单位和餐桌,限制分别转化为和源点汇点的边容量,单位到餐桌的边容量为1

思路2:贪心,我们先处理人多的单位,把人安置到空位多的座位上,口胡一下用优先队列维护方便点

6.星际转移

思路:枚举天数,然后拆点,将每个空间站拆成第一天的空间站x、第二天的空间站x…… ,然后匹配的点连边,容量为限制人数。

当最大流跑满k时输出天数

注意地球和月球无法走通的情况,我一开始用的并查集判的,不过其实是错的

可能存在联通但是无法到达的情况,因为飞船的移动是具有周期性的,不能断言如果存在联通则一定能到月球,存在死循环的情况,当天数足够大时break就行

Ps:发现并查集写错了,然后翻博客把以前的代码贴上去,又发现以前的代码是错的。。。翻了一下模板,模板的代码也是错的。。。。。

以下题非24题里的QAQ

abc231H

题意:H*W的矩阵,(ai,bi)的位置上有个花费为ci的物品,要求选择若干物品,覆盖每行和每列的最小费用是多少?(H,W<=1e3)

Solution:最大费用可行流

刚开始在最小割方向想,发现建图有点困难,又想了下最小路径覆盖,不过这里带权。二分图匹配做不了。。

首先容易想到分成行和列两个集合,然后先贪心地对每行每列用最小的费用去覆盖掉。再考虑能不能减少费用,也就是去找增广路。

设min[i]表示覆盖掉i点的最小费用

到这用最大费用可行流,对于(ai,bi,ci)我们连一条(ai,bi+H)权值为min[a[i]] +min[b[i]+H] - c[i]的边(边权要乘-1),所有容量都设为1。

因为对于每个点外卖都先贪心地单独覆盖掉了,剩下来的就是花更少的费用去匹配掉一些点,容量设为1,是去找每行能不能找一下增广路,对于i行,它只需要考虑自己(因为现在每行每列都被覆盖了),然后在考虑自己的过程中顺便就也顺便匹配掉了j列,因为是费用最大流,那费用优先,找到最大费用就ok了。

#define int long long
const int maxn=2e3+10;
bool vis[maxn];
int n,m,s,t,x,y,z,f,dis[maxn],pre[maxn],last[maxn],flow[maxn],maxflow,mincost;
struct Edge{
    int to,next,flow,dis;//flow流量 dis花费
}edge[100000];
int head[maxn],num_edge;
queue <int> q;
void add_edge(int from,int to,int flow,int dis){
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    edge[num_edge].flow=flow;
    edge[num_edge].dis=dis;
    head[from]=num_edge;

    edge[++num_edge].next=head[to];
    edge[num_edge].to=from;
    edge[num_edge].flow=0;
    edge[num_edge].dis=-dis;
    head[to]=num_edge;
}
bool spfa(int s,int t){
    fill(dis,dis+1+t,-inf);
    fill(vis,vis+1+t,0);
    fill(last,last+1+t,-1);
    q.push(s); vis[s]=1; dis[s]=0;flow[s]=inf;
    while (!q.empty()){
        int now=q.front();
        q.pop();
        vis[now]=0;
        for (int i=head[now]; i!=-1; i=edge[i].next){
            if( edge[i].flow<=0 ) continue;
            if (  dis[edge[i].to]<dis[now]+edge[i].dis){
                // cout<<" "<<edge[i].to<<endl; 
                dis[edge[i].to]=dis[now]+edge[i].dis;
                last[edge[i].to]=i;
                flow[edge[i].to]=min(flow[now],edge[i].flow);
                if (!vis[edge[i].to]){
                    vis[edge[i].to]=1;
                    q.push(edge[i].to);
                }
            }
        }
    }
    return dis[t]>0;
}
void MCMF(){
    while (spfa(s,t)){
        int now=t;
        maxflow+=flow[t];
        mincost+=flow[t]*dis[t];
        while (now!=s){//从源点一直回溯到汇点
            edge[last[now]].flow-=flow[t];//flow和dis容易搞混
            edge[last[now]^1].flow+=flow[t];
            now = edge[last[now]^1].to;
        }
    }
}
int mi[maxn],a[maxn],b[maxn],c[maxn];
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif 
    mst(head,-1);
    num_edge=1;
    int H,W;cin>>H>>W>>n;
    _for(i,1,H+W) mi[i] = inf;
    _for(i,1,n){
        cin>>a[i]>>b[i]>>c[i];
        mi[a[i]] = min(mi[a[i]],c[i]);
        mi[b[i]+H] = min(mi[b[i]+H],c[i]);
    }
    int ans = 0;
    s = 0 , t = H+W+1;
    _for(i,1,H){
         add_edge(s,i,1,0);
         ans += mi[i];
    }
    _for(i,1,W){
         add_edge(i+H,t,1,0);
         ans += mi[i+H];
    }
    _for(i,1,n){
        add_edge(a[i],b[i]+H,1,mi[a[i]]+mi[b[i]+H]-c[i]);
    }
    MCMF();
    ans -= mincost;
    cout<<ans<<endl;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值