[SDOI2014]LIS(最小割)
阿新 • • 發佈:2020-07-30
[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\)
- 對於\(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)\)是可行的割邊
關鍵程式碼如下:
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
*/