洛谷P2766:最长不下降子序列问题

问题描述

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

(1)计算其最长不下降子序列的长度s。

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

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

«编程任务:

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

思路与想法

      看到这道题,我想到的只是推DP方程,只要保持n的平方就可以完成了(但是不行,最少n的三次方)。

      网络流中的最大流可以解决这个东西,只要保证从原点流到汇点的流量满足s长度的最长非下降就可以了。怎么保证呢?

解法

      我们把第一问的答案用n的平方(或者大佬可以用单调队列来维护这个max)。f[i]表示以i结尾的最长非下降序列长度是多少。(拆点,把 i 拆成 i 和 i’)

      1.当f[i]==1时,那么表示i不能作为一个序列的结尾,只能作为开头,所以从begin(源点)到i连一条流量为1的边。

      2.当f[i]==mmax时,表示i能作为一个最长非下降序列的结尾,所以从i’到end(汇点)连一条流量为1的边。

      3.当a[i]

      4.为了能使流量能连续流通,我们从i’到i连一条流量为1的边。

(注意,当mmax==1 时,要一定要特判,从i到i’连一条流量为1的边;因为不这样子的话,i到end就没有边可以通,一开始我就卡了两个点。。。)

第三个问,就干脆将begin到1点连一条INF的边,n’点到end连一条INF的边。

想看具体图片吗?。。

像下面这组样例

4
3 6 2 5

洛谷P2766:最长不下降子序列问题_第1张图片

#include
#include
#include
#include
using namespace std;

int n;
int a[510];
int f[510];
struct edge{int y,next,c;};
edge s[600010];
int begin,end;
int first[10010];
int len=1;
int q[10010];
int h[10010];
bool tf[10010];
int mmax=0;
int st,ed;

int mmin(int x,int y)
{
	return x0)
			{
				h[y]=h[x]+1;
				q[ed]=y;
				ed++;
				if(ed==n+n+n) ed=1;
			}
		}
		st++;
		if(st==n+n+n) st=1;
	}
	if(h[end]!=0) return true;
	return false;
}

int dfs(int x,int t)
{
	if(x==end) return t;
	int tot=0;
	for(int i=first[x];i!=0;i=s[i].next)
	{
		if(t==tot) return t;
		int y=s[i].y;
		if(h[y]==h[x]+1)
		{
			int get=dfs(y,mmin(t-tot,s[i].c));
			tot+=get;s[i].c-=get;s[i^1].c+=get;
		}
	}
	if(tot==0) h[x]=0;
	return tot;
}

int max_flow()
{
	int flow=0,now=0;
	while(bfs())
	{
		now=dfs(begin,1e9);
		while(now!=0)
		{
			flow+=now;
			now=dfs(begin,1e9);
		}
	}
	return flow;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
		f[i]=1;
		for(int j=1;jf[i]) f[i]=f[j]+1; 
		if(f[i]>mmax) mmax=f[i];
	}
	printf("%d\n",mmax);
	begin=0;
	end=2*n+1;
	build_edge(1);
	printf("%d\n",max_flow());
	build_edge(1e9);
	printf("%d\n",max_flow());
}


你可能感兴趣的:(洛谷P2766:最长不下降子序列问题)