luogu3308,LOJ 2196 [SDOI2014]LIS
给出序列\(a,b,c\),现在需要选择一些数将其删除,要求删掉之后\(a\)的最长上升子序列是原来的减一。代价是\(b\)的和。
在最小化代价之后,最小化选择的数按照\(c\)排序之后的字典序。
\(n\le 700\)
学习网络流退流的时候搜到了这题。(为什么直接搜就只有这道例题啊)
按照套路算出\(f_i\)表示\(i\)结尾的最长上升子序列。根据\(f_i\)建分层图,并且\(S\)向\(f_i=1\)的点连边,\(f_i=mx\)的点向\(T\)连边。现在要求\(S\)到\(T\)的任何路径上都存在被选的点。
显然是个最小割。问题是后面的字典序。
假设枚举\(i\)是否在答案中。首先看\(i\to i'\)是否能在最小割集中,判断在残量网络中是否连通即可。如果在最小割集中,删掉这条边,然后重新跑最大流。
重新跑最大流太慢了,可以退流:假设要删去边\(u,v\),其流量(同时也是容量)为\(c\),那么\(flow(T,v,c),flow(u,S,c)\)(\(flow\)表示源点为哪里,汇点为哪里跑个上限为多少的最大流)。
这里上限是\(c\),这样就比较好理解。可以想象成退流的时候强制每条增广路都经过这条边。
然而大多数博客这里都没有标明上限是\(c\)甚至代码中直接标了个inf
。这让我苦恼了许久因为这应该是错的所以我认为它们是有些细节没有说出来。然而它们确实过了。
举一个简单的反例(前面部分代码省略):
int main(){
int u,v;
m=4;
s=1,u=2,v=3,t=4;
link(s,u,1),link(u,s,0);
link(u,v,1),link(v,u,0);
link(s,v,100),link(v,s,0);
link(v,t,1000),link(t,v,0);
printf("%d\n",flow(s,t));
printf("%d %d %d\n",flow(t,v),flow(u,s),reach(s,t));
return 0;
}
跑出结果:
101
101 1 0
最后这个0
是不寻常的,显然删掉边\((u,v)\)之后还存在路径\(S\to v\to T\)。
然而为什么它们过了呢?因为这题建图方式比较特殊,要删的边\((u,v)\)中\(v\)的入边是唯一的,\(flow(T,v,\infty)\)也只能是入边的流量。
LOJ上很容易过了,luogu上半天卡不过去。lyl-sap和dinic都试过了一遍。
后来给dinic加了个重要优化:BFS的时候如果到了\(T\)就直接退出。
大概是因为先前是尽量在跑长度较小的增广路,后面反着跑的时候距离会小很多?
然后它过了。
另外我也给lyl-sap加了优化。因为lyl-sap一开始在没有遍历到\(T\)时,会不断迭代加深搜索,这样会耗时间。实际上可以先跑遍bfs,记\(dis_T=d\),则将所有\(dis_x\)改成\(\max(d-dis_x,0)\),\(gap\)相应地改一下,如果\(dis_x>0\)则cur[x]=last[x]
。这个优化在配上类似上面那个到了\(T\)就退出的优化之后也能过。
dinic:
using namespace std;
#include <bits/stdc++.h>
#define N 705
#define INF 1000000000
#define ll long long
int n;
int a[N],b[N],c[N],p[N];
bool cmpp(int x,int y){return c[x]<c[y];}
int f[N];
int m,s,t;
struct EDGE{
int to,c;
EDGE *las;
} e[N*N+N*3*2];
int ne;
EDGE *last[N*2];
void link(int u,int v,int c){
e[ne]={v,c,last[u]};
last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
EDGE *de[N];
EDGE *cur[N*2];
int dis[N*2];
int S,T;
bool bfs(int s,int t){
static queue<int> q;
for (int i=1;i<=m;++i)
cur[i]=last[i];
memset(dis,127,sizeof(int)*(m+1));
while (!q.empty()) q.pop();
q.push(s);
dis[s]=0;
while (!q.empty()){
int x=q.front();
q.pop();
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->c && dis[ei->to]>=INF){
dis[ei->to]=dis[x]+1;
if (ei->to==t)
return 1;
q.push(ei->to);
}
}
return 0;
}
int dfs(int x,int s){
if (x==T)
return s;
int have=0;
for (EDGE *&ei=cur[x];ei;ei=ei->las)
if (ei->c && dis[ei->to]==dis[x]+1){
int t=dfs(ei->to,min(ei->c,s-have));
ei->c-=t,rev(ei)->c+=t,have+=t;
if (have==s)
return s;
}
return have;
}
ll flow(int _S,int _T,int lim=INF){
S=_S,T=_T;
memset(cur,0,sizeof(EDGE*)*(m+1));
ll res=0;
while (lim && bfs(S,T)){
int t=dfs(S,lim);
res+=t,lim-=t;
}
return res;
}
int ls[N],cnt;
int main(){
freopen("in.txt","r",stdin);
int Q;
scanf("%d",&Q);
while (Q--){
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]);
int mx=0;
for (int i=1;i<=n;++i){
f[i]=1;
for (int j=1;j<i;++j)
if (a[j]<a[i])
f[i]=max(f[i],f[j]+1);
mx=max(mx,f[i]);
}
m=n*2;
s=++m,t=++m;
ne=0;
memset(last,0,sizeof(EDGE*)*(m+1));
for (int i=1;i<=n;++i){
if (f[i]==1)
link(s,i,INF),link(i,s,0);
de[i]=e+ne;
link(i,i+n,b[i]),link(i+n,i,0);
if (f[i]==mx)
link(i+n,t,INF),link(t,i+n,0);
for (int j=i+1;j<=n;++j)
if (a[i]<a[j] && f[j]==f[i]+1)
link(i+n,j,INF),link(j,i+n,0);
}
ll ans=flow(s,t);
for (int i=1;i<=n;++i)
p[i]=i;
sort(p+1,p+n+1,cmpp);
cnt=0;
for (int i=1;i<=n;++i){
int x=p[i];
if (!bfs(x,x+n)){
ls[++cnt]=x;
flow(t,x+n,b[x]);
flow(x,s,b[x]);
de[x]->c=rev(de[x])->c=0;
}
}
sort(ls+1,ls+cnt+1);
printf("%lld %d\n",ans,cnt);
for (int i=1;i<=cnt;++i)
printf("%d ",ls[i]);
printf("\n");
}
return 0;
}
lyl-sap:
using namespace std;
#include <bits/stdc++.h>
#define N 705
#define INF 1000000000
#define ll long long
int n;
int a[N],b[N],c[N],p[N];
bool cmpp(int x,int y){return c[x]<c[y];}
int f[N];
int m,s,t;
struct EDGE{
int to,c;
EDGE *las;
} e[N*N+N*3*2];
int ne;
EDGE *last[N*2];
void link(int u,int v,int c){
e[ne]={v,c,last[u]};
last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
EDGE *de[N];
EDGE *cur[N*2];
int dis[N*2],BZ,gap[N*2];
int S,T;
int dfs(int x,int s){
if (x==T)
return s;
int have=0;
for (EDGE *&ei=cur[x];ei;ei=ei->las)
if (ei->c && dis[ei->to]+1==dis[x]){
int t=dfs(ei->to,min(ei->c,s-have));
ei->c-=t,rev(ei)->c+=t,have+=t;
if (have==s)
return s;
}
cur[x]=last[x];
if (!--gap[dis[x]])
BZ=0;
++dis[x];
++gap[dis[x]];
return have;
}
bool setdis(int S,int T){
static queue<int> q;
while (!q.empty()) q.pop();
q.push(S);
memset(dis,127,sizeof(int)*(m+1));
dis[S]=0;
while (!q.empty()){
int x=q.front();
q.pop();
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->c && dis[ei->to]>=INF){
dis[ei->to]=dis[x]+1;
if (ei->to==T)
return 1;
q.push(ei->to);
}
}
return 0;
}
ll flow(int _S,int _T,int lim=INF){
S=_S,T=_T;
if (!setdis(S,T))
return 0;
int d=dis[T];
memset(gap,0,sizeof(int)*(m+1));
for (int i=1;i<=m;++i){
dis[i]=max(d-dis[i],0);
gap[dis[i]]++;
cur[i]=(dis[i]?last[i]:0);
}
BZ=1;
ll res=0;
while (BZ && lim){
int t=dfs(S,lim);
res+=t,lim-=t;
}
return res;
}
int ls[N],cnt;
int main(){
// freopen("in.txt","r",stdin);
int Q;
scanf("%d",&Q);
while (Q--){
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]);
int mx=0;
for (int i=1;i<=n;++i){
f[i]=1;
for (int j=1;j<i;++j)
if (a[j]<a[i])
f[i]=max(f[i],f[j]+1);
mx=max(mx,f[i]);
}
m=n*2;
s=++m,t=++m;
ne=0;
memset(last,0,sizeof(EDGE*)*(m+1));
for (int i=1;i<=n;++i){
if (f[i]==1)
link(s,i,INF),link(i,s,0);
de[i]=e+ne;
link(i,i+n,b[i]),link(i+n,i,0);
if (f[i]==mx)
link(i+n,t,INF),link(t,i+n,0);
for (int j=i+1;j<=n;++j)
if (a[i]<a[j] && f[j]==f[i]+1)
link(i+n,j,INF),link(j,i+n,0);
}
ll ans=flow(s,t);
for (int i=1;i<=n;++i)
p[i]=i;
sort(p+1,p+n+1,cmpp);
cnt=0;
for (int i=1;i<=n;++i){
int x=p[i];
if (!setdis(x,x+n)){
ls[++cnt]=x;
flow(t,x+n,rev(de[x])->c);
flow(x,s,rev(de[x])->c);
de[x]->c=rev(de[x])->c=0;
}
}
sort(ls+1,ls+cnt+1);
printf("%lld %d\n",ans,cnt);
for (int i=1;i<=cnt;++i)
printf("%d ",ls[i]);
printf("\n");
}
return 0;
}