[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 
*/
posted @ 2020-07-30 15:16  birchtree  阅读(198)  评论(0编辑  收藏  举报