洛谷P2766-最长递增子序列问题
chunlvxiong的博客
题目描述:
给定正整数序列x1,...,xn (1≤n≤500)。
1、计算其最长递增子序列的长度s。
2、计算从给定的序列中最多可取出多少个长度为s的递增子序列。
3、如果允许在取出的序列中多次使用x1和xn,则从给定序列中最多可取出多少个长度为s的递增子序列。
思考&分析:
第一问应该比较easy,利用DP求解,时间复杂度O(N^2)--利用线段树可以优化到O(NlogN),但是没这个必要。
第二问:考虑使用网络流求解。
首先把所有点x拆成两个点xa,xb,每两个xa,xb之间连一条容量为1的边,以保证每个点只使用1次。
既然要求最多可以取出多少子序列,利用第一问的结果,可以发现显然子序列的任意两项i,j必然满足条件是第一问DP中i是j的最优转移。这很好理解,子序列的任意两项肯定彼此贴近才好,否则岂不是有中间项空缺。所以对于这样的i,j,连接一条ib到ja的边。
起点也肯定是那些DP值为1的点(记为点i),连接一条s到ia的边。
最大递增子序列唯一可能的结束点就是DP值为最大递增子序列长度的点(记为点i),连接一条ib到t的边。
然后跑最大流求解即可。
第三问:实际上只是在第二问的基础上更改了几个条件。
由于x1,xn可以使用多次,所以(s,x1.a),(x1.b,t),(s,xn.a),(xn.b,t),(x1.a,x1.b),(xn.a,xn.b)这些边(如果存在的话),其容量应为oo。
然后再跑一次最大流求解即可。
但是如果仅仅这样做,在洛谷上评测是A不了的,原因如下:
1、不知道为何,所有的“递增子序列”被改成了“非递减子序列”???
2、一种特殊的情况,整个序列是递减序列,此时照道理问题3的答案应该为无穷大,然而答案却是n???
总而言之,只有克服了这两个问题,你才能A掉此题。
贴代码:
#include <bits/stdc++.h> using namespace std; const int maxn=1200; const int oo=1e8; int n,a[maxn],dp[maxn]; struct E{ int from,to,next,cap,flow; }edge[maxn*maxn]; int head[maxn],tot; int last[maxn],path[maxn],dis[maxn],num[maxn]; int s,t,Ans=0; void init(){ memset(head,-1,sizeof(head)),tot=0; } void makedge(int u,int v,int w){ edge[tot].from=u,edge[tot].to=v; edge[tot].cap=w,edge[tot].flow=0; edge[tot].next=head[u],head[u]=tot++; edge[tot].from=v,edge[tot].to=u; edge[tot].cap=0,edge[tot].flow=0; edge[tot].next=head[v],head[v]=tot++; } int ISAP(){ int u=s,v,break_p,Min; for (int i=1;i<=n;i++) last[i]=head[i]; memset(dis,0,sizeof(dis)); memset(num,0,sizeof(num)),num[0]=n*2+2; while (dis[s]<n*2+2){ if (u==t){ Min=oo; for (u=t;u!=s;u=edge[path[u]].from) if (edge[path[u]].cap-edge[path[u]].flow<Min) Min=edge[path[u]].cap-edge[path[u]].flow,break_p=edge[path[u]].from; Ans+=Min; for (u=t;u!=s;u=edge[path[u]].from) edge[path[u]].flow+=Min,edge[path[u]^1].flow-=Min; u=break_p; } bool find=false; for (int i=last[u];i!=-1;i=edge[i].next) if (edge[i].cap>edge[i].flow){ v=edge[i].to; if (dis[v]==dis[u]-1){ path[v]=last[u]=i,u=v,find=true; break; } } if (!find){ Min=n*2+3; for (int i=head[u];i!=-1;i=edge[i].next) if (edge[i].cap>edge[i].flow) v=edge[i].to,Min=min(Min,dis[v]+1); if (--num[dis[u]]==0) break; num[dis[u]=Min]++,last[u]=head[u]; if (u!=s) u=edge[path[u]].from; } } return Ans; } int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); int len=0; for (int i=1;i<=n;i++){ dp[i]=1; for (int j=1;j<i;j++) if (a[i]>=a[j]) dp[i]=max(dp[i],dp[j]+1); len=max(len,dp[i]); } printf("%d\n",len); s=0,t=1,init(); for (int i=1;i<=n;i++) makedge(i*2,i*2+1,1); for (int i=1;i<=n;i++){ if (dp[i]==1) makedge(s,i*2,1); if (dp[i]==len) makedge(i*2+1,t,1); } for (int i=1;i<n;i++){ for (int j=i+1;j<=n;j++) if (a[i]<=a[j] && dp[i]+1==dp[j]) makedge(i*2+1,j*2,1); } printf("%d\n",ISAP()); if (dp[1]==1) makedge(s,2,oo); if (dp[1]==len) makedge(3,t,oo); if (dp[n]==1) makedge(s,n*2,oo); if (dp[n]==len) makedge(n*2+1,t,oo); makedge(2,3,oo),makedge(n*2,n*2+1,oo); int res=ISAP(); if (res>oo) printf("%d\n",n); else printf("%d\n",res); return 0; }