luogu2766(dp+网络流)

第一问dp就不说了。。

然后这个网络流的建图思路就有点神奇了。。

第一问的LIS用的是传统的N^2dp做。。然后可以算出以每个数为结尾的LIS,记为dp[i],将第一问答案记做s

然后其实答案最多就是dp[i]等于s的个数,然后剩下的就是看他们能不能找到自己前面的子列了。。

首先考虑找s-1位置的数字,显然是要找dp[i]==s-1的数字,如果这个i找不到s位置的数字,那么这个数还是否有用呢?答案是没有。。可能很多人想过这个数字如果不能放在s-1的位置那可以放在更靠前的位置继续使用,然而假设真的有更靠前的位置给它放,那么我们大可以把这个数以前的序列全部替换成以他为结尾的LIS序列,然后你会发现,替换之后整个序列的长度就大于s了。。这显然不合理。。

所以可以得到一个结论,对每个数i他只能放在序列中的dp[i]位置。。

然后建图思路就十分明了了,直接将dp值差1而且可以在dp的时候转移的2个点连起来,容量为1,然后将dp值为1的和源点连边,dp值为s的和汇点连边,容量均为1。。

看起来貌似建完了,不过需要注意的是每个数字只能用一次,因此对每个点还需要拆点限制流量。。





/**
 *        ┏┓    ┏┓
 *        ┏┛┗━━━━━━━┛┗━━━┓
 *        ┃       ┃  
 *        ┃   ━    ┃
 *        ┃ >   < ┃
 *        ┃       ┃
 *        ┃... ⌒ ...  ┃
 *        ┃       ┃
 *        ┗━┓   ┏━┛
 *          ┃   ┃ Code is far away from bug with the animal protecting          
 *          ┃   ┃   神兽保佑,代码无bug
 *          ┃   ┃           
 *          ┃   ┃        
 *          ┃   ┃
 *          ┃   ┃           
 *          ┃   ┗━━━┓
 *          ┃       ┣┓
 *          ┃       ┏┛
 *          ┗┓┓┏━┳┓┏┛
 *           ┃┫┫ ┃┫┫
 *           ┗┻┛ ┗┻┛
 */ 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inc(i,l,r) for(int i=l;i<=r;i++)
#define dec(i,l,r) for(int i=l;i>=r;i--)
#define link(x) for(edge *j=h[x];j;j=j->next)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define eps 1e-12
#define succ(x) (1<>1)
#define NM 20005
#define nm 500005
#define pi 3.1415926535897931
using namespace std;
const int inf=1000000005;
ll read(){
    ll x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return f*x;
}





struct edge{int t,v;edge*next,*rev;}e[nm],*h[NM],*o=e,*tmp[NM],*p[NM];
void _add(int x,int y,int v){o->t=y;o->v=v;o->next=h[x];h[x]=o++;}
void add(int x,int y,int v){_add(x,y,v);_add(y,x,0);h[x]->rev=h[y];h[y]->rev=h[x];}
int n,a[NM],dp[NM],ans,d[NM],cnt[NM],tot;

int maxflow(){
	int flow=0;edge*j;
	mem(d);mem(cnt);mem(tmp);mem(p);cnt[0]=tot=n+1;
	inc(i,0,n)tmp[i]=h[i];
	for(int x=0,s=inf;d[x]next)if(j->v&&d[x]==d[j->t]+1)break;
		if(j){
			s=min(s,j->v);p[j->t]=tmp[x]=j;
			if((x=j->t)==n){
				for(;p[x];x=p[x]->rev->t)p[x]->v-=s,p[x]->rev->v+=s;
				flow+=s;s=inf;
			}
		}else{
			if(!--cnt[d[x]])break;d[x]=tot;
			link(x)if(j->v&&d[x]>d[j->t]+1)tmp[x]=j,d[x]=d[j->t]+1;
			cnt[d[x]]++;
			if(p[x])x=p[x]->rev->t;
		}
	}
	return flow;
}


int main(){
	n=read();
	inc(i,1,n)a[i]=read();
	inc(i,1,n){
		dp[i]=1;
		inc(j,1,i-1)if(a[i]>=a[j])dp[i]=max(dp[i],dp[j]+1);
		ans=max(ans,dp[i]);
	}
	printf("%d\n",ans);
	inc(i,1,n)if(dp[i]==1)add(0,i,1);
	inc(i,1,n)if(dp[i]==ans)add(i+n,2*n+1,1);
	inc(i,1,n)inc(j,i+1,n)if(a[i]<=a[j]&&dp[i]+1==dp[j])add(i+n,j,1);
	inc(i,1,n)add(i,i+n,1);
	int _t=n;n=2*n+1;int _s;
	printf("%d\n",_s=maxflow());
	add(0,_t+1,inf);if(dp[_t]==ans)add(_t,n,inf);
	printf("%d\n",_s+maxflow());
	return 0;
}


P2766 最长不下降子序列问题


题目描述

«问题描述:

给定正整数序列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

说明

n≤500n\le 500n500


你可能感兴趣的:(DP,网络流)