CF1383F Special Edges 题解
网络流
Statement
CF1383F Special Edges - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
给一张带边权的点数为 \(n\),边数为 \(m\) 的有向图,又给定 \(k\) 条边为特殊边,特殊边的边权开始为 \(\rm 0\)。有 \(q\) 次修改,每次修改为:给定一个长度为 \(k\) 的序列 \(\{w_k\}\),表示第 \(i\) 条特殊边的边权变为 \(w_i\)。每次修改后 求出节点 \(1\) 到节点 \(n\) 的最大流。
\(2\le n\le 10^4,1\le m\le 10^4,1\le k\le\min(10,m),1\le q\le 2\times 10^5,w_i\le 25\) ,其中 \(w_i\) 为某一条边的边权(包括非特殊边)
Solution
我们容易得到一个暴力 \(O(qn^2m)\) 的每次暴力重跑一遍 \(Dinic\) 的做法
显然我们每次没有必要全部暴力重跑,注意到 \(k\le 10,w_i\le 25\) 的特殊数据范围
由最大流等于最小割定理,点 \(1\) 到点 \(n\) 的最大流,等于边权和最小的边集 \(E\) 的边权和,满足割掉 \(E\) 中的边后 \(1\) 与 \(n\) 不连通,我们考虑求最小割。
显然,这 \(k\) 条边要么在割中,要么不在割中,可以用二进制表示。
我们有这样的思路:预处理出 \(f[sta]\) 表示强制令 \(sta\) 代表的那些特殊边不能割,其他特殊边边全割的最大流,那么最小割就是 \(\min(f[sta]+g[!sta])\) ,这里 \(!\) 表示取反,\(g[sta]\) 表示 \(sta\) 中特殊边的权值,也就是在割中的特殊边的权值
每条特殊边选和不选, \(sta\) 一共有 \(2^k\) 中状态
最初的图上,所有特殊边的权值都是 \(\rm0\) ,我们可以认为它们都在割中,此时直接跑一遍 \(Dinic\) 求出 \(f[0]\)
设 \(E[sta]\) 表示强制 \(sta\) 不在割中,跑完最大流得到的残量网络
考虑枚举到一个 \(f[i]\) ,我们可以认为他是在 \(f[i\oplus(lowbit[i])]\) 的基础上把特殊边 \(lowbit[i]\) 的权值设为 \(inf\) ,再在 \(E[i\oplus (lowbit[i])]\) 这个残量网络上跑一手最大流 \(flow\),那么 \(f[i]=f[i\oplus(lowbit[i])]+flow\)
当然,由于 \(w_i\le 25\) ,所以强制一条边不在割中我们也可以设权值 \(25\) 就好
这样的复杂度是什么样子的呢?如果我们直接跑 \(Dinic\) 的话,显然要 G,但是由于 \(w_i\le 25\) ,我们不妨直接跑 \(FF\) ,跑一次复杂度 \(O(wm)\)
所以总复杂度就是 \(O(n^2m+2^kwm+q2^k)\) ,开头一遍 \(Dinic\) ,预处理 \(f[]\) ,询问
Code
本题卡常,所以 C++14 真的暴打 C++17/20,手写队列暴打 std::queue,建议加上读写优化和火车头
这里的实现参考了 题解 CF1383F 【Special Edges】 - Soulist 的博客 - 洛谷博客 (luogu.com.cn)
#include<bits/stdc++.h>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
const int N = 1e4+5;
const int K = (1<<10)|5;
const int inf = 1e8+7;
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+ch-'0',ch=getchar();
return s*w;
}
void print(int x) {
if(x>9)print(x/10);
*O++=x%10+'0';
}
struct Edge{
int nex,to,dis;
}edge[N<<1],es[2][270][N<<1];//es 滚动记录 $f[sta]$ 的残量网络
struct Line{int u,v,w;}l[15];//特殊边
int head[N],cur[N],dep[N],pre[N],pro[N];
int H[K][N],ecnt[K],f[K],g[K],bit[K],id[K],q[N];
int n,m,k,q_,elen=1,ans,lim;
void addedge(int u,int v,int w){
edge[++elen]={head[u],v,w},head[u]=elen;
edge[++elen]={head[v],u,0},head[v]=elen;
}
namespace Dinic{
bool bfs(){
for(int i=1;i<=n;++i)dep[i]=0; q[1]=dep[1]=1;
for(int he=1,ed=1,u;u=q[he],he<=ed;++he)
for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)
if(edge[e].dis&&!dep[v])dep[v]=dep[u]+1,q[++ed]=v;
return dep[n]!=0;
}
int dfs(int u,int flow){
if(u==n)return flow; int res=0;
for(int&e=cur[u],v;v=edge[e].to,e;e=edge[e].nex)
if(dep[v]==dep[u]+1&&edge[e].dis){
int tmp=dfs(v,min(flow,edge[e].dis));
edge[e].dis-=tmp,flow-=tmp;
edge[e^1].dis+=tmp,res+=tmp;
if(!flow)return res;
}
return res;
}
int dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=n;++i)cur[i]=head[i];
ans+=dfs(1,inf);
}
return ans;
}
}
namespace EK{
int bfs(){
for(int i=1;i<=n;++i)dep[i]=0; q[1]=dep[1]=1;
for(int he=1,ed=1,u;u=q[he],he<=ed;++he)
for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)
if(edge[e].dis&&!dep[v]){
dep[v]=dep[u]+1,pre[v]=u,pro[v]=e,q[++ed]=v;
if(v==n)break;
}
if(!dep[n])return 0; int mn=25,now=n;
while(now!=1)mn=min(mn,edge[pro[now]].dis),now=pre[now]; now=n;
while(now!=1)edge[pro[now]].dis-=mn,edge[pro[now]^1].dis+=mn,now=pre[now];
return mn;
}
int ek(){
int ans=0,flow;
while(flow=bfs())ans+=flow;
return ans;
}
}
namespace FF{
bool vis[N];
int dfs(int u,int flow){
if(u==n)return flow; vis[u]=true;
for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)
if(edge[e].dis&&!vis[v]){
int tmp=dfs(v,min(flow,edge[e].dis));
if(tmp>0)return edge[e].dis-=tmp,edge[e^1].dis+=tmp,tmp;
}
return 0;
}
int ff(){
int ans=0,flow;
while(1){
for(int i=1;i<=n;++i)vis[i]=false;
flow=dfs(1,inf);
if(!flow)return ans;
ans+=flow;
}
}
}
signed main(){
srand(time(0));
n=read(),m=read(),k=read(),q_=read(),lim=1<<k;
for(int i=1;i<=k;++i)l[i].u=read(),l[i].v=read(),l[i].w=read();
for(int i=k+1,u,v,w;i<=m;++i)u=read(),v=read(),w=read(),addedge(u,v,w);
ecnt[0]=elen,f[0]=Dinic::dinic();
for(int i=1;i<=elen;++i)es[0][0][i]=edge[i];//一定要先跑最大流再 copy
for(int i=1;i<=n;++i)H[0][i]=head[i];
for(int siz=1,ids;ids=0,siz<=k;++siz) //枚举特殊边数量,因为我们 f[i] 从 popcount 少 1 的地方转移
for(int i=1;i<lim;++i)if(__builtin_popcountll(i)==siz){
if(rand()%2==0){for(int j=k;j>=1;--j)if(i&(1<<(j-1))){bit[i]=j-1;break;}}
else {for(int j=1;j<=k;++j)if(i&(1<<(j-1))){bit[i]=j-1;break;}}//或直接 bit[i]=lowbit(i);
//这里是一个随机化卡常,看是取最后一条边还是第一条边
int bef=i^(1<<bit[i]);
elen=ecnt[bef],id[i]=++ids;
for(int j=1;j<=elen;++j)edge[j]=es[(siz&1)^1][id[bef]][j];
for(int j=1;j<=n;++j)head[j]=H[bef][j];
addedge(l[bit[i]+1].u,l[bit[i]+1].v,25);
f[i]=FF::ff()+f[bef];//比 f[i]=EK:ek()+f[bef] 要快
ecnt[i]=elen;
for(int j=1;j<=n;++j)H[i][j]=head[j];
for(int j=1;j<=elen;++j)es[siz&1][id[i]][j]=edge[j];
}
while(q_--){
for(int i=1;i<=k;++i)l[i].w=read(); ans=inf;
for(int i=1;i<lim;++i)g[i]=g[i^(1<<bit[i])]+l[bit[i]+1].w;//
for(int i=0;i<lim;++i)ans=min(ans,f[i]+g[i^(lim-1)]);
print(ans),*O++='\n';//快输
}
fwrite(obuf,O-obuf,1,stdout);
return 0;
}