最长递增子序列问题

问题描述

    给定正整数序列x1,···,xn。
    (1)计算其最长递增子序列的长度s。
    (2)计算从给定的序列中最多可取出多少个长度为s的递增子序列。
    (3)如果允许在取出的序列中多次使用x1和 xn,则从给定序列中最多可取出多少个长度为s的递增子序列。

编程任务

    设计有效算法完成(1)(2)(3)提出的计算任务。

数据输入

    由文件input.txt提供输入数据。文件第1行有1个正整数n,表示给定序列的长度。接下来的1行有n个正整数x1,···,xn。

结果输出

    程序运行结束时,将任务(1)(2)(3)的解答输出到文件 output.txt中。第1 行是最长递增子序列的长度s。第2行是可取出的长度为s的递增
子序列个数。第3行是允许在取出的序列中多次使用x1和xn时可取出的长度为s的递增子序列个数。

输入文件示例

input.txt 
4
3 6 2 5

输出文件示例

output.txt
2
2
3

题解

问题(1)可以用dp进行求解。
对于问题(2)和(3),我们可以用网络流解决。对于每一个数字,我们都看作节点并进行拆点,以达到限流的目的。对于每一个dp值为1,也就是以这个数为结尾的最长上升子序列长度为1,的节点,我们可以从源点引一条边连向这个点。同理,对于每一个dp值最大的节点,我们从它引一条边连向汇点。这种建图方式也蕴含了分层图的思想。(即从源点开始,bfs到的点分别是dp为1、2、3的点)
因为我们要构建分层图,所以对于每一个f[i]==f[j]+1,s[i]>=s[j] (j < i)的情况,我们从j拆点后的点引一条边连向i点(本人一开始连边的时候是从i的拆点指向j,但是去掉f[i]==f[j]+1的条件可以得到82分,我能说是数据太水了么)

CODE:

#include<cstdio>
#include<cstring>
const int INF=1e9;
struct queue
{
    int h,t;
    int a[10001];
    inline void clear(){h=1,t=0;}
    inline void push(int n){a[++t]=n;}
    inline int front(){return a[h];}
    inline void pop(){h++;}
    inline bool empty(){return h>t;}
}q;
struct edge
{
    int next,to,remain;
}a[200001];
int head[1001];
int deep[1001];
int s[1001];
int f[1001];
int n,num=1,ans,S,T;
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline void add(int x,int y,int cap)
{
    a[++num].next=head[x],a[num].to=y,a[num].remain=cap,head[x]=num;
    a[++num].next=head[y],a[num].to=x,head[y]=num;
}
inline bool bfs()
{
    memset(deep,0x3f,sizeof(deep));
    deep[S]=0;
    q.clear();q.push(S);
    while(!q.empty())
    {
        int tmp=q.front();q.pop();
        for(int i=head[tmp];i;i=a[i].next)
          if(deep[a[i].to]>INF&&a[i].remain)
            q.push(a[i].to),deep[a[i].to]=deep[tmp]+1;
    }
    return deep[T]<INF;
}
int dfs(int now,int limit)
{
    if(now==T||!limit) return limit;
    int flow=0,f;
    for(int i=head[now];i;i=a[i].next)
      if(deep[a[i].to]==deep[now]+1&&(f=dfs(a[i].to,min(limit,a[i].remain))))
      {
        limit-=f,flow+=f,a[i].remain-=f,a[i^1].remain+=f;
        if(!limit) return flow;
      }
    deep[now]=-1;
    return flow;
}
inline int dinic()
{
    int ans=0;
    while(bfs()) ans+=dfs(S,INF);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
      scanf("%d",&s[i]);
    f[1]=1;
    for(int i=2;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
          if(s[j]<=s[i]) f[i]=max(f[i],f[j]+1);
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    //1~n:原点 n+1~2*n:拆点 2*n+1:源点 2*n+2:汇点 
    S=n<<1|1;T=S+1;
    for(int i=1;i<=n;i++)
      add(i,n+i,1);
    for(int i=1;i<=n;i++)
    {
        if(f[i]==1) add(S,i,1);
        if(f[i]==ans) add(n+i,T,1);
    }
    for(int i=2;i<=n;i++)
      for(int j=1;j<i;j++)
        if(s[j]<=s[i]&&f[j]==f[i]-1) add(n+j,i,1);
    ans=dinic();printf("%d\n",ans);
    for(int i=head[S];i;i=a[i].next)
      if(a[i].to==1) {a[i].remain=INF;break;}
    for(int i=head[1];i;i=a[i].next)
      if(a[i].to==n+1) {a[i].remain=INF;break;}
    for(int i=head[n<<1];i;i=a[i].next)
      if(a[i].to==T) {a[i].remain=INF;break;}
    for(int i=head[n];i;i=a[i].next)
      if(a[i].to==n<<1) {a[i].remain=INF;break;}
    ans+=dinic();printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值