zoukankan      html  css  js  c++  java
  • Libre 6005 「网络流 24 题」最长递增子序列 / Luogu 2766 最长递增子序列问题(网络流,最大流)

    Libre 6005 「网络流 24 题」最长递增子序列 / Luogu 2766 最长递增子序列问题(网络流,最大流)

    Description

    问题描述:

    给定正整数序列x1,...,xn 。

    (1)计算其最长递增子序列的长度s。

    (2)计算从给定的序列中最多可取出多少个长度为s的递增子序列。

    (3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的递增子序列。

    编程任务:

    设计有效算法完成(1)(2)(3)提出的计算任务。

    Input

    第1 行有1个正整数n,表示给定序列的长度。接下来的1 行有n个正整数n:x1, ..., xn。

    Output

    第1 行是最长递增子序列的长度s。第2行是可取出的长度为s 的递增子序列个数。第3行是允许在取出的序列中多次使用x1和xn时可取出的长度为s 的递增子序列个数。

    Sample Input

    4
    3 6 2 5

    Sample Output

    2
    2
    3

    Http

    Libre:https://loj.ac/problem/6005
    Luogu:https://www.luogu.org/problem/show?pid=2766

    Source

    网络流,最大流

    解决思路

    看清题目,不是最长递增子序列是最长不下降子序列。
    这道题目首先运用动态规划的方式求出最长不下降子序列,这也是第一问的内容。注意,本题不能使用单调队列的方式,因为要求出到每一个数的最长不下降子序列长度(后面记为F),这在后两问中要用。
    那么如何求解第二问呢?
    我们把每一个数拆成两个点入点和出点,在每一个数的入点和出点之间连容量为1的边,同时设置一个源点一个汇点。从前往后扫描每一个数,若发现第i个数的F[i]最长不下降子序列长度,则在源点与i的出点之间连一条容量为1的边。若F[i]1,则在其出点与汇点之间连一条容量为1的边。并且,对于任何数i,扫描其前面的每一个数j,若F[i]==F[j]+1且第j位的数<=第i位的数,则在i的出点与j的入点之间连一条容量为1的边。
    这样建图,有点类似于分层图的思想,从最高层的F为最长不下降子序列长度,往下每一层长度减一,直到最底下一层长度为1。这样我们跑一边最短路就可以了。
    至于第三问,我们只要在重新建图的时候把1到汇点,1的入点到出点,n的入点到出点,源点到n(如果存在的话)这几条边设置为无穷大即可。
    但要注意,第三问要特判一下递减的情况,因为这样最长不下降子序列长度为1,跑最大流会出现无穷大的流的情况。
    另:这里用Dinic求解最大流,具体请移步我的这篇文章

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxN=2000;
    const int maxM=maxN*maxN*4;
    const int inf=147483647;
    
    class Edge
    {
    public:
    	int u,v,flow;
    };
    
    int n;
    int cnt=-1;
    int F[maxN];
    int Arr[maxN];
    int Head[maxN];
    int Next[maxM];
    Edge E[maxM];
    int depth[maxN];
    int cur[maxN];
    int Q[maxM];
    
    void Add_Edge(int u,int v,int flow);
    bool bfs();
    int dfs(int u,int flow);
    
    int main()
    {
    	scanf("%d",&n);
    	for (int i=1;i<=n;i++)
    		scanf("%d",&Arr[i]);
    	for (int i=1;i<=n;i++)//动态规划求出最长不下降子序列
    	{
    		F[i]=1;
    		for (int j=1;j<i;j++)
    		    if (Arr[j]<=Arr[i])
    				F[i]=max(F[i],F[j]+1);
    	}
    	int maxlength=1;
    	for (int i=1;i<=n;i++)
    		maxlength=max(maxlength,F[i]);//得出第一问答案
    	printf("%d
    ",maxlength);
    	memset(Head,-1,sizeof(Head));
    	for (int i=1;i<=n;i++)//构造第二问的图
    	{
    		Add_Edge(i,i+n,1)//连接入点和出点
    		if (F[i]==maxlength)//若与最长长度相同,则连接源点
    			Add_Edge(0,i,1);
    		if (F[i]==1)//若为最小长度1,则连接汇点
    			Add_Edge(i+n,n*2+1,1);
    		for (int j=1;j<i;j++)
    			if ((F[j]==F[i]-1)&&(Arr[j]<=Arr[i]))//向前统计能连的
    				Add_Edge(i+n,j,1);
    	}
    	int Ans=0;//求解最大流
    	while (bfs())
    	{
    		for (int i=0;i<=2*n+1;i++)
    			cur[i]=Head[i];
    		while (int di=dfs(0,inf))
    			Ans+=di;
    	}
    	cout<<Ans<<endl;
    	memset(Head,-1,sizeof(Head));
    	cnt=-1;
    	for (int i=1;i<=n;i++)//构造第三问的图
    	{
    		int nowflow=1;
    		if ((i==1)||(i==n))//1和n的流量为无穷大
    			nowflow=inf;
            if (maxlength==1)//注意这里特判递减序列的情况
                Add_Edge(i,i+n,1);
            else
                Add_Edge(i,i+n,inf);
    		if (F[i]==maxlength)
    			Add_Edge(0,i,nowflow);
    		if (F[i]==1)
    			Add_Edge(i+n,n*2+1,nowflow);
    		for (int j=1;j<i;j++)
    			if ((F[j]==F[i]-1)&&(Arr[j]<=Arr[i]))
    				Add_Edge(i+n,j,1);
    	}
    	Ans=0;//求解最大流
    	while (bfs())
    	{
    		for (int i=0;i<=2*n+1;i++)
    			cur[i]=Head[i];
    		while (int di=dfs(0,inf))
    			Ans+=di;
    	}
    	cout<<Ans<<endl;
    	return 0;
    }
    
    void Add_Edge(int u,int v,int flow)
    {
    	cnt++;
    	Next[cnt]=Head[u];
    	Head[u]=cnt;
    	E[cnt].u=u;
    	E[cnt].v=v;
    	E[cnt].flow=flow;
    
    	cnt++;
    	Next[cnt]=Head[v];
    	Head[v]=cnt;
    	E[cnt].v=u;
    	E[cnt].u=v;
    	E[cnt].flow=0;
    }
    
    bool bfs()
    {
    	memset(depth,-1,sizeof(depth));
    	int h=1,t=0;
    	Q[1]=0;
    	depth[0]=1;
    	do
    	{
    		t++;
    		int u=Q[t];
    		//cout<<u<<endl;
    		for (int i=Head[u];i!=-1;i=Next[i])
    		{
    			int v=E[i].v;
    			if ((depth[v]==-1)&&(E[i].flow>0))
    			{
    				depth[v]=depth[u]+1;
    				h++;
    				Q[h]=v;
    			}
    		}
    	}
    	while (t!=h);
    	if (depth[n*2+1]==-1)
    		return 0;
    	return 1;
    }
    
    int dfs(int u,int flow)
    {
    	if (u==n*2+1)
    		return flow;
    	for (int &i=cur[u];i!=-1;i=Next[i])
    	{
    		int v=E[i].v;
    		if ((depth[v]==depth[u]+1)&&(E[i].flow>0))
    		{
    			int di=dfs(v,min(flow,E[i].flow));
    			if (di>0)
    			{
    				E[i].flow-=di;
    				E[i^1].flow+=di;
    				return di;
    			}
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    ios开发启动页面
    C++再学习之路(五)
    C++再学习之路(四)
    opencv再学习之路(九)---制作对焦图像
    opencv再学习之路(八)---轮廓检测
    C++再学习之路---例程(一) 文本查询
    opencv再学习之路(五)---灰度直方图显示
    opencv再学习之路(七)---图像单个元素的访问
    opencv再学习之路(六)---模板匹配
    C++再学习之路(三)
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7280857.html
Copyright © 2011-2022 走看看