网络流24题(持续更新

Posted 吃花椒的妙酱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流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();
}

以上是关于网络流24题(持续更新的主要内容,如果未能解决你的问题,请参考以下文章

网络流24题

网络流题目详讲+题单(提高版)(持续更新中......)

网络流24题(好大的坑啊)

巨坑网络流线性规划与网络流24题

[网络流24题] 运输问题

网络流24题 软件补丁问题