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

最长不下降子序列

题目大意

在给出的长为 \(n\) 的序列中完成以下三个任务

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

  • 如果每个元素只允许被取出一次,求最多可以取出多少个长度为 \(s\) 的不下降子序列

  • 如果序列中第一个和最后一个元素允许被多次取出,求最多可以取出多少个长度为 \(s\) 的不下降子序列

题目分析

第一问

第一个问题是比较容易解决的,由于 \(n\) 很小,所以我们可以考虑一个时间复杂度为 \(O(n^2)\) 的算法。

\(f[i]\) 表示以 \(a[i]\) 结尾的最长不下降子序列的长度,则在遍历 \(i-n\) 的过程中,我们可以查询 \(j<i\) 中满足 \(a[j]<=a[i]\)\(f[j]\) 的最大值,而 \(f[i]\) 就等于它的值加一。

最后再在\(f[i]\)中寻找最大值即可。

具体代码如下:

//解决问题一,由于数据很小暴力n^2即可解决
for(register int i=1; i<=n; i++) a[i]=read();
for(register int i=1; i<=n; i++) {
	for(register int j=1; j<i; j++)
		if(a[i]>=a[j]) f[i]=max(f[i],f[j]);
	f[i]++;
	ans=max(f[i],ans); //更新答案
}

第二问

第二问中,要求我们不能把每个元素重复取出,我们很自然的想到了网络流的拆点操作可以解决此类要求,于是考虑网络流算法。

将一个点 \(i\) 拆成 \(i\)\(i'\) ,在它们之间连上一条容量为 \(1\) 的边,表示每个点只能被取用一次。

然后,为了满足我们找到的序列都是不下降子序列,再联想到第一问中处理出来的 \(f\) 数组,则我们可以对 \(f[i]=f[j]+1\)\(a[j]\leq a[i]\) 的点在它们之间建立一条容量为 \(1\) 的边将 \(j'\)\(i\) 连接起来。

最后,再建立超级源点超级汇点,用超级源点向每个满足 \(f[i]=1\) 的点连边,每个满足 \(f[i]=s\) 的点再向超级汇点连边。

以上便是建图全过程,最后再跑一遍网络最大流即可。

代码如下:

//解决问题二 
//建图
//超级源点设为0,超级汇点设为2*n+1
s=0,t=2*n+1;
//将1~n全部拆为两个点,点i的拆点为i+n
//如果f[i]为1,则向超级源点连一条边
//如果f[i]为s,则向超级汇点连一条边 
for(register int i=1;i<=n;i++){
	if(f[i]==1) Add(s,i,1),Add(i,s,0);
	if(f[i]==ans) Add(i+n,t,1),Add(t,i+n,0);
}
//设节点i的拆点为i'
//则若a[j]<=a[i]&&f[j]+1=f[i]则j'向i连接一条边 
for(register int i=1;i<=n;i++)
	for(register int j=1;j<i;j++)
		if(a[i]>=a[j]&&f[i]==f[j]+1) Add(j+n,i,1),Add(i,j+n,0);
//连接自己 
for(register int i=1;i<=n;i++) Add(i,i+n,1),Add(i+n,i,0);

第三问

明白了第二问,第三问就比较容易了,只用把超级源点连向节点 \(1\) 的边的容量改为正无穷,节点 \(n\) 连向超级汇点的边的容量同样改为正无穷,还要注意更换他们内部自身的连边也改为正无穷即可。

CODE

#include <bits/stdc++.h>
using namespace std;
const int N=5e2+10;
int n,s,t,ans,maxf1,maxf2;
int a[N],f[N]; //f[i]表示以a[i]结尾最长不下降子序列的长度
inline int read() {
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int deep[2*N*N],head[2*N*N];
int tot=-1,v[2*N*N],w[2*N*N],nex[2*N*N],first[2*N*N];
inline void Add(int x,int y,int z)
{
	nex[++tot]=first[x];
	first[x]=tot;
	v[tot]=y,w[tot]=z;
}
inline bool BFS()
{
	for(register int i=0;i<=2*n+1;i++) deep[i]=-1;
	queue<int> q;
	q.push(s);
	deep[s]=0;
	while(!q.empty()){
		int now=q.front(); q.pop();
		for(register int i=first[now];i!=-1;i=nex[i])
			if(w[i]>0&&deep[v[i]]==-1) deep[v[i]]=deep[now]+1,q.push(v[i]);
	}
	return deep[t]!=-1;
}
inline int DFS(int S,int T,int flow)
{
	if(S==T) return flow;
	int res=0;
	for(register int& i=head[S];i!=-1;i=nex[i]){
		if(deep[v[i]]==deep[S]+1){
			int temp=DFS(v[i],T,min(flow,w[i]));
			w[i]-=temp;
			w[i^1]+=temp;
			flow-=temp;
			res+=temp;
			if(!flow) return res;
		}
	}
	return res;
}
inline void dinic1() //第一问 
{
	while(BFS()){
		for(register int i=0;i<=2*n+1;i++) head[i]=first[i];
		maxf1+=DFS(s,t,1e9);
	}
}
inline void dinic2() //第二问 
{
	while(BFS()){
		for(register int i=0;i<=2*n+1;i++) head[i]=first[i];
		maxf2+=DFS(s,t,1e9);
	}
}
int main()
{
	n=read();
	for(register int i=0;i<=2*n+1;i++) first[i]=-1;	
	//解决问题一,由于数据很小暴力n^2即可解决
	for(register int i=1; i<=n; i++) a[i]=read();
	for(register int i=1; i<=n; i++) {
		for(register int j=1; j<i; j++)
			if(a[i]>=a[j]) f[i]=max(f[i],f[j]);
		f[i]++;
		ans=max(f[i],ans); //更新答案
	}
	//解决问题二 
	//建图
	//超级源点设为0,超级汇点设为2*n+1
	s=0,t=2*n+1;
	//将1~n全部拆为两个点,点i的拆点为i+n
	//如果f[i]为1,则向超级源点连一条边
	//如果f[i]为s,则向超级汇点连一条边 
	for(register int i=1;i<=n;i++){
		if(f[i]==1) Add(s,i,1),Add(i,s,0);
		if(f[i]==ans) Add(i+n,t,1),Add(t,i+n,0);
	}
	//设节点i的拆点为i'
	//则若a[j]<=a[i]&&f[j]+1=f[i]则j'向i连接一条边 
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<i;j++)
			if(a[i]>=a[j]&&f[i]==f[j]+1) Add(j+n,i,1),Add(i,j+n,0);
	//连接自己 
	for(register int i=1;i<=n;i++) Add(i,i+n,1),Add(i+n,i,0);
	dinic1();
	//对于第三问,只需将1,n两个数 
	for(register int i=0;i<=2*n+1;i++) first[i]=-1;
	for(register int i=0;i<=tot;i++) nex[i]=0;
	for(register int i=0;i<=tot;i++) v[i]=0;
	for(register int i=0;i<=tot;i++) w[i]=0;
	tot=-1;
	for(register int i=1;i<=n;i++){
		if(f[i]==1){
			if(i==1) Add(s,i,1e9),Add(i,s,0);
			else Add(s,i,1),Add(i,s,0);
		}
		if(f[i]==ans){
			if(i==n) Add(i+n,t,1e9),Add(t,i+n,0);
			else Add(i+n,t,1),Add(t,i+n,0); 
		}
	}
	for(register int i=1;i<=n;i++)
		for(register int j=1;j<i;j++)
			if(a[i]>=a[j]&&f[i]==f[j]+1) Add(j+n,i,1),Add(i,j+n,0);
	for(register int i=1;i<=n;i++){
		if(i==1||i==n) Add(i,i+n,1e9),Add(i+n,i,0);
		else Add(i,i+n,1),Add(i+n,i,0);
	}
	dinic2();
	if(n==1) maxf2=1;
	printf("%d\n%d\n%d\n",ans,maxf1,maxf2);
	return 0;
}
posted @ 2021-02-18 16:30  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(57)  评论(0编辑  收藏  举报