1 条题解

  • 0
    @ 2025-8-24 22:02:24

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Siyuan
    Dream OIer.

    搬运于2025-08-24 22:02:24,当前版本为作者最后更新于2018-12-17 19:14:56,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    $$\Large\texttt{My Blog}$$


    题目链接:Luogu 4662

    Byteland 国警方收到了一条匿名举报,其中说当地黑帮老大正计划一次从港口到郊区仓库的运输。警方知道运输的时间并且知道运输需要用到国家的高速公路网。

    高速公路网包含 nn 个收费站和 mm 条双向的高速公路,每个路段直接连着两个不同的收费站。一个收费站可能与很多其他的收费站相连。汽车只能通过收费站进入或离开高速公路网。据所知,黑帮会距港口边最近的收费站进入高速公路,从距仓库最近的收费站离开(不会在出高速后重新进入)。特警组位于选定的收费站处。当运输车辆进入被监控的收费站时,它就会被警察抓住。

    从这个角度看,最简单的办法就是在每个收费站处都安排特警班。然而,控制第 ii 个收费站需要特定的费用 cic_i,每个收费站费用不同。警方想要让花费最小,所以他们需要制定一个收费站的最小控制集,这个集合满足两个条件:

    • 所有从港口到仓库的交通必须至少经过集合中的一个收费站。
    • 监控这些收费站的费用(即监控每一个收费站费用之和)最小。
    • 你可以假设使用高速公路可以从港口到仓库。

    你需要找到收费站的最小控制集。

    数据范围:1n2001\le n\le 2001m2×1041\le m\le 2\times 10^41ci1071\le c_i\le 10^7


    Solution

    我们需要将一些收费站安排特警班,使得源点和汇点不连通,于是这个问题就是最小割问题。

    我们考虑如何将割点转化成割边。由于每个点只能被割一次,我们首先需要拆点:将点 ii 拆成 i1i_1i2i_2 两个点,其中 i1i_1i2i_2 之间连容量为代价(本题中代价为 11)的边。对于对于一条边 (u,v)(u,v),也就转化为 (u2,v1,INF)(u_2,v_1,\texttt{INF})(v2,u1,0)(v_2,u_1,0),其中容量为 INF\texttt{INF} 使得这条边不可能被割掉,即割掉的边一定为 (i1,i2)(i_1,i_2) 这样的边。

    由于源点和汇点也可以被割,所以源点转化为 s1s_1,汇点转化为 t2t_2

    最后我们证明一下正确性。此时,整张图的形态没有改变,每个点只能被割一次对应着每条边 (i1,i2)(i_1,i_2) 只能被割一次,正确性得证。

    对于控制集,我们从源点 s1s_1 出发沿着残量大于 00 的边开始 DFS\texttt{DFS},将所有访问的点打标记。对于一条正向边 (u,v)(u,v),如果 uu 被打了标记而 vv 没有被打标记,那么就意味着这条边所代表的点被割掉了。

    时间复杂度O(n2m)O(n^2m)


    Code

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    #include <vector>
    
    const int N=4e2+5,M=1e5+5;
    const int INF=1<<30;
    int n,m,tot=1,lnk[N],ter[M],nxt[M],val[M],dep[N],cnr[N];
    bool vis[N];
    
    void add(int u,int v,int w) {
        ter[++tot]=v,nxt[tot]=lnk[u],lnk[u]=tot,val[tot]=w;
    }
    void addedge(int u,int v,int w) {
        add(u,v,w),add(v,u,0);
    }
    int bfs(int s,int t) {
        memset(dep,0,sizeof(dep));
        memcpy(cnr,lnk,sizeof(lnk));
        std::queue<int> q;
        q.push(s),dep[s]=1;
        while(!q.empty()) {
            int u=q.front(); q.pop();
            for(int i=lnk[u];i;i=nxt[i]) {
                int v=ter[i];
                if(val[i]&&!dep[v]) q.push(v),dep[v]=dep[u]+1;
            }
        }
        return dep[t];
    }
    int dfs(int u,int t,int flow) {
        if(u==t) return flow;
        int ans=0;
        for(int i=cnr[u];i&&ans<flow;i=nxt[i]) {
            cnr[u]=i;
            int v=ter[i];
            if(val[i]&&dep[v]==dep[u]+1) {
                int x=dfs(v,t,std::min(val[i],flow-ans));
                if(x) val[i]-=x,val[i^1]+=x,ans+=x;
            }
        }
        if(ans<flow) dep[u]=-1;
        return ans;
    }
    void dinic(int s,int t) {
        while(bfs(s,t)) while(dfs(s,t,INF));
    }
    void dfs(int u) {
        vis[u]=1;
        for(int i=lnk[u];i;i=nxt[i]) if(val[i]&&!vis[ter[i]]) dfs(ter[i]);
    }
    int main() {
        int s,t;
        scanf("%d%d%d%d",&n,&m,&s,&t);
        for(int i=1;i<=n;++i) {
            int x;
            scanf("%d",&x);
            addedge(i,i+n,x);
        }
        while(m--) {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u+n,v,INF),addedge(v+n,u,INF);
        }
        dinic(s,t+n);
        dfs(s);
        std::vector<int> ans;
        for(int i=2;i<=tot;i+=2) {
            int u=ter[i^1],v=ter[i];
            if(vis[u]&&!vis[v]) ans.push_back(u);
        }
        std::sort(ans.begin(),ans.end());
        for(int sz=(int)ans.size()-1,i=0;i<=sz;++i) {
            printf("%d%c",ans[i]," \n"[i==sz]);
        }
        return 0;
    }
    
    • 1

    信息

    ID
    3640
    时间
    1000ms
    内存
    32MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者