洛谷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;
}

 

posted @ 2017-08-16 22:38  chunlvxiong  阅读(559)  评论(1编辑  收藏  举报