[网络流24题]最长递增子序列问题

本文介绍了如何使用网络流算法解决最长递增子序列问题,包括求解最长递增子序列的长度,以及在不同限制下可取出的递增子序列个数。通过动态规划和网络流建模,给出详细代码实现。

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

最长递增子序列问题
题目描述:

给定正整数序列x1,...,xn 。
(1)计算其最长递增子序列的长度s。
(2)计算从给定的序列中最多可取出多少个长度为s的递增子序列。
(3)如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的递增子序列。
设计有效算法完成(1)(2)(3)提出的计算任务。

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

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

输入样例#1:
4
3 6 2 5

输出样例#1:
2
2
3

题解:
(1).用dp解决就可以了,f[i]为以i为终点最长子序列的长度,再简单不过了。
(2).网络流解决吧,首先根据题意“取出”可知每个数只能选一次那么就直接想到拆点了(后面讲不拆点的方法)连边分四种情况:①若f[i]=1将i和s连一条容量为1的边。②若f[i]=s,将i+n和t连一条容量为1的边。③若f[i]=f[j]+1 && w[i]<w[j] 将j+n和i连一条容量为1的边。④将i和i+n连边
(3).第三问还比较好做,既然不限制使用次数,将容量设成inf就可以了。
还有拆点是必须拆的,因为出现重复选择的情况(但是没拆点的代码竟然过了?可能数据太水了吧)。例如:
5
8 1 9 11 10
建图如图


代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

const int max_m = 3000001;
const int max_n = 3001;

int f[max_n],w[max_n];
int point[max_n],nxt[max_m],v[max_m],remain[max_m];
int deep[max_n],cur[max_n];
int n,x,y,tot,s,t,inf,maxn,ans;

inline void clear()
{
	memset(point,-1,sizeof(point));
	memset(nxt,-1,sizeof(nxt));
	for(int i=1; i<=n; ++i) f[i]=1;
	tot=-1; inf=1e9; s=0; maxn=1; t=2*n+1; f[n]=1;
}

inline void addedge(int x,int y,int cal)
{
	++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=cal;
	++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;
}

inline int dfs(int now,int t,int limit)
{
	if(now==t || !limit) return limit;
	int flow=0,f;
	
	for(int i=cur[now]; i!=-1; i=nxt[i])
	  if(deep[v[i]]==deep[now]+1 && (f=dfs(v[i],t,min(remain[i],limit))))
	  {
	  	flow+=f;
	  	limit-=f;
	  	remain[i]-=f;
	  	remain[i^1]+=f;
	  	if(!limit) break;
	  }
	  
	return flow;
}

inline bool bfs(int s,int t)
{
	memset(deep,0x7f,sizeof(deep));
	for(int i=s; i<=t; ++i)
	  cur[i]=point[i];
	
	queue<int> q;
	deep[s]=0; q.push(s);
	
	while(!q.empty())
	{
		int now=q.front(); q.pop();
		for(int i=point[now]; i!=-1; i=nxt[i])
		  if(deep[v[i]]>inf && remain[i])
		  {
		  	deep[v[i]]=deep[now]+1;
		  	q.push(v[i]);
		  }
	}
	
	return deep[t]<inf;
} 

inline int dinic(int s,int t)
{
	int ans=0;
	
	while(bfs(s,t))
	  ans+=dfs(s,t,inf);
	  
	return ans;
}

int main()
{
	
	scanf("%d",&n);
	clear();
	
	for(int i=1; i<=n; ++i)
	  scanf("%d",&w[i]);
	  
	for(int i=1; i<=n; ++i)//dp求最长递增子序列 
	  for(int j=1; j<i; ++j)
	    if(w[j]<=w[i])
	    {
	    	f[i]=max(f[j]+1,f[i]);
	    	maxn=max(maxn,f[i]);
		}
	printf("%d\n",maxn);
	
	for(int i=1; i<=n; ++i)
		addedge(i,i+n,1);
	
	for(int i=1; i<=n; ++i)
	{
		if(f[i]==1) addedge(s,i,1);
		if(f[i]==maxn) addedge(i+n,t,1);
	}
	
	for(int i=1; i<=n; ++i)
	  for(int j=1; j<i; ++j)
	    if(w[j]<=w[i] && f[i]==f[j]+1) addedge(j+n,i,1); 
	printf("%d\n",ans=dinic(s,t));
	
	addedge(s,1,inf);//将1和n容量设为inf 
	addedge(1,1+n,inf);
	if(f[n]==maxn)
	{
		addedge(2*n,t,inf);
		addedge(n,2*n,inf);
	}
	
	
	printf("%d\n",dinic(s,t)+ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值