1. 程式人生 > >【LIS+最大流】LOJ - #6005. 最長遞增子序列

【LIS+最大流】LOJ - #6005. 最長遞增子序列

題目連結<https://loj.ac/problem/6005>


題意:

給定正整數序列 x1∼xn ​,以下遞增子序列均為非嚴格遞增。

  1. 計算其最長遞增子序列的長度s。
  2. 計算從給定的序列中最多可取出多少個長度為 s 的遞增子序列。
  3. 如果允許在取出的序列中多次使用x1和xn,則從給定序列中最多可取出多少個長度為s的遞增子序列。

題解:

  • 第一問直接秒掉,寫成upperbound即可。
  • 第二問基於第一問的dp陣列,源點與dp為1的連一條權值為1的邊,dp為s的與匯點連一條權值為1的邊。對於每一個點i,與它後面的點j且a[i]<=a[j]&&dp[j]==dp[i]+1連線一條權值為1的邊。這樣跑網路流就相當於模擬了。因為只能每個點只能取一次,所以還要拆點(其實不拆點也能水過)。
  • 第三問就把兩個點的容量限制變為無窮就行了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
const int inf=1e9+7;
struct Edge{
    int v,w,nxt;
    Edge(int v=0,int w=0,int nxt=0):v(v),w(w),nxt(nxt){}
}e[N*30];
int edn,sp,tp;
int p[N],d[N],c[N];
void add(int u,int v,int w){
    e[++edn]=Edge(v,w,p[u]);p[u]=edn;
    e[++edn]=Edge(u,0,p[v]);p[v]=edn;
}
bool bfs(){
    memset(d,-1,sizeof(d));d[sp]=0;
    queue<int>q;q.push(sp);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=p[u];~i;i=e[i].nxt){
            int v=e[i].v;
            if(d[v]==-1&&e[i].w){
                d[v]=d[u]+1;
                q.push(v);
                if(v==tp) return true;
            }
        }
    }
    return ~d[tp];
}
int dfs(int u,int b){
    if(u==tp) return b;
    int r=0;
    for(int i=c[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(e[i].w&&d[v]==d[u]+1){
            int x=min(e[i].w,b-r);
            c[u]=i;
            x=dfs(v,x);
            r+=x;
            e[i].w-=x;
            e[i^1].w+=x;
            if(r==b) break;
        }
    }
    if(!r) d[u]=-2;
    return r;
}
int dinic(){
    int tot=0,t;
    while(bfs()){
        memcpy(c,p,sizeof(p));
        while(t=dfs(sp,inf))
            tot+=t;
    }
    return tot;
}
int a[N],g[N],dp[N],n;
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        g[i]=inf;
    }
    g[n]=inf;
    for(int i=0;i<n;i++){
        int k=upper_bound(g+1,g+1+n,a[i])-g;
        dp[i]=k;
        g[k]=a[i];
    }
    int ans=0;
    for(int i=0;i<n;i++)
        ans=max(ans,dp[i]);
    printf("%d\n",ans);
    memset(p,-1,sizeof(p));edn=-1;
    sp=2*n+1,tp=sp+1;
    for(int i=0;i<n;i++){
        if(dp[i]==1) add(sp,i,1);
        if(dp[i]==ans) add(i+n,tp,1);
        for(int j=i+1;j<n;j++){
            if(a[i]<=a[j]&&dp[j]==dp[i]+1)
                add(i+n,j,1);
        }
        add(i,i+n,1);
    }
    printf("%d\n",dinic());
    memset(p,-1,sizeof(p));edn=-1;
    sp=n*2+1,tp=sp+1;
    for(int i=0;i<n;i++){
        if(dp[i]==1){
            if(i==0) add(sp,i,inf);
            else add(sp,i,1);
        }
        if(dp[i]==ans){
            if(i==n-1) add(i,tp,inf);
            else add(i+n,tp,1);
        }
        for(int j=i+1;j<n;j++){
            if(a[i]<=a[j]&&dp[j]==dp[i]+1)
                add(i+n,j,1);
        }
        if(i==0||i==n-1) add(i,i+n,inf);
        else add(i,i+n,1);
    }
    printf("%d\n",dinic());
}