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;
}
posted @ 2022-02-11 10:20  _Famiglistimo  阅读(45)  评论(0编辑  收藏  举报