[SDOI2014]LIS(最小割)
题面
给定序列A,序列中的每一项Ai有删除代价Bi和附加属性Ci。请删除若干项,使得A的最长上升子序列长度减少至少1,且付出的代价之和最小,并输出方案。 如果有多种方案,请输出将删去项的附加属性排序之后,字典序最小的一种。
(n leq 700)
分析
先做一次LIS的DP,设(f_i)表示以(i)结尾的LIS长度,序列LIS长度为(len)。那么类似网络流24题中的最长不下降子序列问题建图
把每个数拆成入点(i)和出点(i+n)
- 对于(f_i=1)的所有(i), 连边((s,i,+infin))
- 对于(f_i=len)的所有(i),连边((i+n,t,infin))
- 对于(j<i),若$a_i>a_j,f_i=f_j+1 (,连边)(j+n,i,infin)$
- 对于每个(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
",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
",(int)ans.size());
sort(ans.begin(),ans.end());
for(int x:ans) printf("%d ",x);
printf("
");
}
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
*/