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

本文介绍了一种利用网络流算法解决最长非下降子序列问题的方法,包括计算最长子序列长度及其出现次数,并考虑了特定元素重复使用的特殊情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

给定正整数序列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]<a[j]  and(&&)   f[j]=f[i]+1  的时候,那么从i到j‘连一条流量为1的边。

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

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

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

想看具体图片吗?。。

像下面这组样例

4
3 6 2 5

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
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 x<y?x:y;
}

void ins(int x,int y,int c)
{
	len++;
	s[len].y=y;s[len].c=c;
	s[len].next=first[x];first[x]=len;
	len++;
	s[len].y=x;s[len].c=0;
	s[len].next=first[y];first[y]=len;
}

void build_edge(int x)
{
	memset(first,0,sizeof(first));
	len=1;
	for(int i=1;i<=n;i++)
		if(f[i]==1) 
			if(i==1)
				ins(begin,i,x);
			else
				ins(begin,i,1);
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			if(a[i]<=a[j] && (f[j]==f[i]+1 || mmax==1)) 
				ins(i,n+j,1);
	for(int i=1;i<=n;i++) ins(i+n,i,1);
	for(int i=1;i<=n;i++) 
		if(f[i]==mmax) 
			if(i==n)
				ins(i+n,end,x);
			else
				ins(i+n,end,1);
}

bool bfs()
{
	st=1,ed=2;
	memset(h,0,sizeof(h));
	h[begin]=1;
	q[st]=begin;
	while(st!=ed)
	{
		int x=q[st];
		for(int i=first[x];i!=0;i=s[i].next)
		{
			int y=s[i].y;
			if(h[y]==0 && s[i].c>0)
			{
				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;j<i;j++)
			if(a[j]<=a[i] && f[j]+1>f[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());
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值