题目描述:
给定正整数序列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;
}