1. 程式人生 > >P2766 最長不下降子序列問題 題解(網路流)

P2766 最長不下降子序列問題 題解(網路流)

題目連結

最長不下降子序列問題

解題思路

分成三小問解決。
第一小問,求\(LIS\),因為\(n<=500\),直接\(O(N^2)\)暴力求解即可。
第二三小問,建立模型用網路流求解。
對於第二小問
\((1)\)首先,因為每個點只能使用一次,考慮拆點,把每一個點拆成\(i,n+i\)兩個點,從\(i\)連向\(n+i\)一條長度為\(1\)的有向邊。
\((2)\)其次,因為流向是從S經集合E到T,其中任意集合E中元素\(i\)需要滿足的條件是\(i\)位於LIS上,故:
①出邊從\(lis[i]=k\)的點流向T
②所有的能構成E的邊都需要加入,也就是任意\(i,j\)滿足\(i<j,a[i]<a[j],lis[i]=lis[j]-1\)

都從\(i\)\(j\)連邊。
跑一遍最大流即可。
第三小問唯一的不同就是\(S\)流向\(1\)\(1\)流向\(1+n\)\(n\)流向\(n+n\)\(n+n\)流向\(T\)四條路長度變成\(inf\)而已,改後再跑一遍最大流即可。

AC程式碼

#include<stdio.h>
#include<string.h>
int lis[1020],a[1020],len;//lis
int s,t,inf=0x3fffffff;//ek
struct Edge{
    int end,length,near;
}edge[50020];
int head[1020],cnt=2,vis[1020];
void addedge(int begin,int end,int length){
    edge[cnt].end=end;
    edge[cnt].length=length;
    edge[cnt].near=head[begin];
    head[begin]=cnt;
    cnt++;
}
struct Pre{
    int pre,ednum;
}pre[1020];
int bfs(){  
    memset(vis,0,sizeof(vis));
    int queue[50010]={0},headq=0,tail=0,i;
    queue[tail++]=s;
    vis[s]=1;
    while(headq<tail){
        int v=queue[headq];
        for(i=head[v];i;i=edge[i].near){
            int p=edge[i].end;
            if(vis[p]||!edge[i].length)continue;
            vis[p]=1;
            pre[p].pre=v;
            pre[p].ednum=i;
            queue[tail++]=p;
            if(p==t)return 1;
        }
        headq++;
    }
    return 0;
}
int ek(){
    int min,ans=0,i;
    while(bfs()){
        min=inf;
        for(i=t;i!=s;i=pre[i].pre){
            int en=pre[i].ednum;
            if(min>edge[en].length)min=edge[en].length;
        }
        for(i=t;i!=s;i=pre[i].pre){
            int en=pre[i].ednum;
            edge[en].length-=min;
            edge[en^1].length+=min;
        }
        ans+=min;
    }
    return ans;
}
int main(){
    int i,j,n;
    scanf("%d",&n);
    s=2*n+3;
    t=2*n+4;
    for(i=1;i<=n;i++){
        scanf("%d",&a[i]);
        lis[i]=1;
    }
    //lis n<=500 O(N2) 
    for(i=1;i<=n;i++)
        for(j=1;j<i;j++)
            if(a[i]>=a[j]&&lis[i]<lis[j]+1)lis[i]=lis[j]+1;
    for(i=1;i<=n;i++)if(lis[i]>len)len=lis[i];
    printf("%d\n",len);
    //ek1 建圖求最大流 
    for(i=1;i<=n;i++){
        addedge(i,i+n,1);//拆點 
        addedge(i+n,i,0);
        if(lis[i]==1){
            addedge(s,i,1);
            addedge(i,s,0);
        }
        if(lis[i]==len){
            addedge(i+n,t,1);
            addedge(t,i+n,0);
        }
    }
    for(i=1;i<=n;i++){
        for(j=1;j<i;j++){
            if(lis[i]==lis[j]+1&&a[i]>=a[j]){
                addedge(j+n,i,1);   addedge(i,j+n,0);
            }
        }
    }
    printf("%d\n",ek());
    //ek2
    memset(pre,0,sizeof(pre));
    memset(edge,0,sizeof(edge));
    memset(head,0,sizeof(head));
    cnt=2;
    for(i=1;i<=n;i++){
        addedge(i,i+n,1);//拆點 
        addedge(i+n,i,0);
        if(lis[i]==1){
            addedge(s,i,1);
            addedge(i,s,0);
        }
        if(lis[i]==len){
            addedge(i+n,t,1);
            addedge(t,i+n,0);
        }
    }
    for(i=1;i<=n;i++){
        for(j=1;j<i;j++){
            if(lis[i]==lis[j]+1&&a[i]>=a[j]){
                addedge(j+n,i,1);   addedge(i,j+n,0);
            }
        }
    }
    addedge(1,1+n,inf); addedge(1+n,1,0);
    addedge(s,1,inf);   addedge(1,s,0); 
    if(lis[n]==len){
        addedge(n,n+n,inf);     addedge(n+n,n,0);
        addedge(n+n,t,inf);     addedge(t,n+n,0);
    }
    printf("%d\n",ek());
    return 0;
}