[网络流24题]最长不下降子序列问题

题目:洛谷P2766、codevs1906。

题目大意:给你一个数列,要你回答:

1.它的最长不下降子序列;

2.在这个数列中,能取出多少种元素不重复(下标不同即可)的最长不下降子序列;

3.现在规定第一个元素和最后一个元素可以重复利用,问第二问。

解题思路:codevs题面有误,应该是不下降而不是递增。

第一问DP,$O(n^2)$求出f(f[i]表示以第i个元素结尾的最长不下降子序列长度)。

第二、三问都是网络流。

首先对每个元素拆成入点和出点(设分别为i和i+n),然后

对于f[i]=1的i,连接0(超级源点)和i(i的入点),容量1。

对于f[i]=n的i,连接2n(n的出点)和2n+1(超级汇点),容量为1。

然后,对于每一对i<j,若a[i]<=a[j]且f[i]+1=f[j],则连接i+n(i的出点)和j(j的入点),容量为1。

容量为1是因为每个元素只能使用一次。

跑网络流即是第二问的答案。

第三问呢?

由于两个点没有限制,所以把相关边的容量设为inf即可。

所以只要加上0到1,1到n+1的边,和n到2n,2n到2n+1的边(这两条仅当f[n]的值是最长不下降子序列长度时才添加),容量都为inf,

然后在残量网络中继续跑,最后和原来的答案加起来即可。(当然也可以重新构图)。

注意:当原数列严格递减时(即DP答案为1时),答案应该是1,n,n。此时网络流可能跑出问题,需要加特判。

C++ Code:

#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3fffffff
std::queue<int>q;
int a[505],f[505],n,head[1007],cnt=0,level[1007],iter[1007];
struct edge{
	int to,cap,nxt,rev;
}e[1005*1005*2];
inline void addedge(int u,int v,int flow){
	++cnt;
	e[cnt]=(edge){v,flow,head[u],cnt+1};
	head[u]=cnt;
	++cnt;
	e[cnt]=(edge){u,0,head[v],cnt-1};
	head[v]=cnt;
}
void bfs(int s){
	level[s]=1;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i!=-1;i=e[i].nxt)
		if(level[e[i].to]<0&&e[i].cap){
			level[e[i].to]=level[u]+1;
			q.push(e[i].to);
		}
	}
}
int dfs(int u,int t,int f){
	if(u==t)return f;
	for(int& i=iter[u];i!=-1;i=e[i].nxt)
	if(level[e[i].to]>level[u]&&e[i].cap){
		int d=dfs(e[i].to,t,f>e[i].cap?e[i].cap:f);
		if(d){
			e[i].cap-=d;
			e[e[i].rev].cap+=d;
			return d;
		}
	}
	return 0;
}
int maxflow(int s,int t){
	for(int flow=0;;){
		memset(level,-1,sizeof level);
		bfs(s);
		if(level[t]==-1)return flow;
		memcpy(iter,head,sizeof iter);
		int f=0;
		while(f=dfs(s,t,inf))flow+=f;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",&a[i]);
	memset(f,0,sizeof f);
	f[1]=1;
	int ans1=1;
	for(int i=2,j;i<=n;++i)
	for(j=f[i]=1;j<i;++j)
	if(a[j]<=a[i]&&f[j]+1>f[i]){
		f[i]=f[j]+1;
		if(ans1<f[i])ans1=f[i];
	}
	if(ans1==1)return!printf("%d\n%d\n%d\n",1,n,n);
	printf("%d\n",ans1);
	memset(head,-1,sizeof head);
	for(int i=1;i<=n;++i){
		addedge(i,i+n,1);
		if(f[i]==ans1)addedge(i+n,n<<1|1,1);else{
			if(f[i]==1)addedge(0,i,1);
			for(int j=i+1;j<=n;++j)
			if(a[i]<=a[j]&&f[i]+1==f[j])addedge(i+n,j,1);
		}
	}
	int ans2;
	printf("%d\n",ans2=maxflow(0,n<<1|1));
	addedge(0,1,inf);
	if(f[n]==ans1)addedge(n<<1,n<<1|1,inf),addedge(n,n<<1,inf);
	addedge(1,n+1,inf);
	printf("%d\n",ans2+maxflow(0,n<<1|1));
	return 0;
}

 

posted @ 2018-01-07 19:31  Mrsrz  阅读(256)  评论(0编辑  收藏  举报