1. 程式人生 > 實用技巧 >[SDOI2014]LIS(最小割)

[SDOI2014]LIS(最小割)

[SDOI2014]LIS(最小割)

題面

給定序列A,序列中的每一項Ai有刪除代價Bi和附加屬性Ci。請刪除若干項,使得A的最長上升子序列長度減少至少1,且付出的代價之和最小,並輸出方案。 如果有多種方案,請輸出將刪去項的附加屬性排序之後,字典序最小的一種。
\(n \leq 700\)

分析

先做一次LIS的DP,設\(f_i\)表示以\(i\)結尾的LIS長度,序列LIS長度為\(len\)。那麼類似網路流24題中的最長不下降子序列問題建圖

把每個數拆成入點\(i\)和出點\(i+n\)

  1. 對於\(f_i=1\)的所有\(i\), 連邊\((s,i,+\infin)\)
  2. 對於\(f_i=len\)
    的所有\(i\),連邊\((i+n,t,\infin)\)
  3. 對於\(j<i\),若$a_i>a_j,f_i=f_j+1 \(,連邊\)(j+n,i,\infin)$
  4. 對於每個\(i\),連邊\((i,i+n,b_i)\)

這樣從\(s\)\(t\)的一條路徑就代表了一個LIS,刪除項就相當於割斷邊。最小割即為答案。

考慮如何輸出最小割方案。因為要求按\(c_i\)字典序最小,我們把可能的割邊\((i,i+n)\)按照\(c_i\)排序。接著判定,若殘量網路上的邊\((u,v)\)滿流,且從\(u\)\(v\)不存在其他的增廣路(可以經過其他的反向邊),則\((u,v)\)是可行的割邊

。找到一條割邊後,從\(s\)經過\(u,v\)\(t\)上面可能有很多條邊是等價的,因此要讓這些邊不滿流,這樣就不可能成為割邊。於是要把流量退回去,直接從\(u\)\(s\),\(v\)\(t\)跑Dinic即可。

關鍵程式碼如下:

for(int i=1;i<=n;i++){//把可能的割邊按c排序 
    int k=id[i];
    int eid=finde(k,k+n);//找到鄰接表裡割的編號 
    if(!bfs(k,k+n)){//如果從k到k+n不存在其他的增廣路,割掉這條邊之後這個圖才不連通 
        ans.push_back(k);
        dinic(t,k+n);//把k+1到t的流退掉,因為這一條路徑上的割邊是等價的,割掉這條就不需要其他的 
        dinic(k,s);//同理 
        E[eid].flow=E[eid^1].flow=0;
    }
}	

程式碼


#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector> 
#include<algorithm>
#define maxn 1400
#define maxm (maxn*maxn)
#define INF 0x3f3f3f3f
using namespace std;
using namespace std;
typedef long long ll;
struct edge{
    int from;
    int to;
    int next;
    int flow;
}E[maxm*2+5];
int head[maxn+5];
int cur[maxn+5];
int esz=1;
void add_edge(int u,int v,int w){
//	printf("%d->%d %d\n",u,v,w); 
    esz++;
    E[esz].from=u;
    E[esz].to=v;
    E[esz].flow=w;
    E[esz].next=head[u];
    head[u]=esz;
    esz++;
    E[esz].from=v;
    E[esz].to=u;
    E[esz].flow=0;
    E[esz].next=head[v];
    head[v]=esz;
}
int deep[maxn+5];
bool bfs(int s,int t){
    memset(deep,0,sizeof(deep));
    queue<int>q;
    q.push(s);
    deep[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=E[i].next){
            int y=E[i].to;
            if(!deep[y]&&E[i].flow){
                deep[y]=deep[x]+1;
                q.push(y);
                if(y==t) return 1;
            }
        }
    }
    return 0;
}
int dfs(int x,int t,int minf){
    if(x==t) return minf;
    int rest=minf,k;
    for(int &i=cur[x];i;i=E[i].next){
        int y=E[i].to;
        if(E[i].flow&&deep[y]==deep[x]+1){
            k=dfs(y,t,min(rest,E[i].flow));
            E[i].flow-=k;
            E[i^1].flow+=k;
            rest-=k;
            if(k==0) deep[y]=0;
            if(rest==0) break; 
        }
    }
    return minf-rest;
}
ll dinic(int s,int t){
    ll ans=0;
    int now=0;
    while(bfs(s,t)){
        memcpy(cur,head,sizeof(head));
        while((now=dfs(s,t,INF))) ans+=now;
    } 
    return ans;
}

int T,n;
int s,t;
int a[maxn+5],b[maxn+5],c[maxn+5];
int dp[maxn+5];
void ini(){
    esz=1;
    memset(head,0,sizeof(head));
    memset(dp,0,sizeof(dp)); 
}
void build_graph(){
    int len=0;
    s=0,t=2*n+1;
    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[j]+1,dp[i]);
        len=max(len,dp[i]);
    }
    for(int i=1;i<=n;i++){
        add_edge(i,i+n,b[i]);
        if(dp[i]==1) add_edge(s,i,INF);
        if(dp[i]==len) add_edge(i+n,t,INF);
        for(int j=1;j<i;j++) if(a[i]>a[j]&&dp[i]==dp[j]+1) add_edge(j+n,i,INF);
    }
}
int finde(int x,int y){
    for(int i=head[x];i;i=E[i].next)if(E[i].to==y&&E[i].flow==0) return i;
    return 0;
}

void print_sol(){
    static int id[maxn+5];
    vector<int>ans;
    for(int i=1;i<=n;i++) id[i]=i;
    sort(id+1,id+1+n,[](int x,int y)->bool{return c[x]<c[y];});
    for(int i=1;i<=n;i++){//把可能的割邊按c排序 
        int k=id[i];
        int eid=finde(k,k+n);//找到鄰接表裡割的編號 
        if(!bfs(k,k+n)){//如果從k到k+n不存在其他的增廣路,割掉這條邊之後這個圖才不連通 
            ans.push_back(k);
            dinic(t,k+n);//把k+1到t的流退掉,因為這一條路徑上的割邊是等價的,割掉這條就不需要其他的 
            dinic(k,s);//同理 
            E[eid].flow=E[eid^1].flow=0;
        }
    }	
    printf("%d\n",(int)ans.size());
    sort(ans.begin(),ans.end());
    for(int x:ans) printf("%d ",x);
    printf("\n");
} 
int main(){
//	freopen("input.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        ini(); 
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++) scanf("%d",&b[i]);
        for(int i=1;i<=n;i++) scanf("%d",&c[i]);
        build_graph();
        printf("%lld ",dinic(s,t));
        print_sol(); 
    }
} 
/*
1
5
6 5 8 7 3 
8 8 2 8 5 
1 4 2 5 3 
*/