[网络流和线性规划24题]之六 最长递增子序列

时间限制:1秒  内存限制:64M

【问题描述】

  给定正整数序列x1, .., xn。

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

  注意:题目中的“递增”实际上是“非降”,即i < j 且 x[i] <= x[j]。

【输入格式】

  第1 行有 1个正整数n(n<500),表示给定序列的长度。
  接下来的1 行有 n个正整数x1, …, xn.

【输出格式】

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

【输入样例】

4
3 6 2 5

【输出样例】

2
2
3

【数据范围】

n<500

【来源】

[网络流和线性规划24题]之六

这道题很难让人想到网络流,因为容易想复杂。

我一开始在想是不是可以拆成n层点来做比较容易,然后发现会重,这是我们下意识的就应该想到拆点,连流量为1的边,这样每个点就肯定只流了一次了。

现在剩下的问题就是各个点直接的边了,我们有一个第一问用的f数组。

f[i]:以第i个元素开头的最长递增子序列长度。

我们从i往每一个f[j]+1==f[i]&&a[j]>=a[i]的点连边,这样就可以保证不漏了,只需要连f[j]+1==f[i]的不用连其他的,因为我们要的是最长的那几条链,如果一旦有一个可以要的你没要,那它就不可能是最长的那一条了(加上那个点可以更长)。

最后从s往每个f[i]==ans1的点连边,每个f[i]==1的点往t连边就可以跑了。

代码如下:

#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int maxn=2005;
const int inf=200005;

struct edge
{
    int u,v,f,c;
};
vector<edge>e;
vector<int>g[maxn];
int s,t,d[maxn],f[maxn],n,cnt=-1,a[maxn],q[maxn],cur[maxn];

void add(int u,int v,int f){
    e.push_back((edge){u,v,f,0});
    g[u].push_back(++cnt);
    e.push_back((edge){v,u,0,0});
    g[v].push_back(++cnt);
}

bool bfs()
{
    memset(d,0,sizeof(d));
    int root=0,frond=0;
    q[root++]=s;
    d[s]=1;
    while(root!=frond)
    {
        int i=q[frond++];
        int tt=g[i].size();
        for(int k=0;k<tt;k++)
        {
            int id=g[i][k],j=e[id].v;
            if(d[j]||e[id].f==e[id].c) continue;
            d[j]=d[i]+1;
            q[root++]=j;
        }
    }
    return d[t];
}

int dfs(int i,int a)
{
    if(i==t||a==0) return a;
    int flow=0,f,tt=g[i].size();
    for(int &k=cur[i];k<tt;k++)
    {
        int id=g[i][k],j=e[id].v;
        if(d[j]==d[i]+1&&(f=dfs(j,min(a,e[id].f-e[id].c))))
        {
            a-=f;
            flow+=f;
            e[id].c+=f;
            e[id^1].c-=f;
            if(!a) break;
        }
    }
    return flow;
}

int dinic()
{
    int flow=0;
    while(bfs())
    {
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,inf);
    }
    return flow;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=0,ans2;
    for(int i=n;i>=1;i--)
    {
        f[i]=0;
        for(int j=i+1;j<=n;j++) if(a[i]<=a[j]) f[i]=max(f[i],f[j]);
        f[i]++;
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    s=0,t=n*2+1;
    for(int i=1;i<=n;i++)
    {
        add(i,i+n,1);
        if(f[i]==1) add(i+n,t,1);
        if(f[i]==ans) add(s,i,1);
        for(int j=i+1;j<=n;j++)
        if(f[j]+1==f[i]&&a[j]>=a[i]) add(i+n,j,1); 
    }
    ans2=dinic();
    printf("%d\n",ans2);
    add(1,n+1,inf);
    add(n,n*2,inf);
    if(f[1]==ans) add(s,1,inf);
    if(f[n]==1) add(2*n,t,inf);
    ans2+=dinic();
    printf("%d\n",ans2);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值