zoukankan      html  css  js  c++  java
  • [网络流]小M的作物

    (M)的作物(最小割)

    做的第一道网络流,因为一个智障错误调了好久嘤嘤嘤

    题目描述

    (M)(MC)里开辟了两块巨大的耕地(A)(B)(你可以认为容量是无穷),现在,小(P)(n)中作物的种子,每种作物的种子有(1)个(就是可以种一棵作物)(用(1...n)编号)。

    现在,第(i)种作物种植在(A)中种植可以获得(ai)的收益,在(B)中种植可以获得(bi)的收益,而且,现在还有这么一种神奇的现象,就是某些作物共同种在一块耕地中可以获得额外的收益,小(M)找到了规则中共有(m)种作物组合,第(i)个组合中的作物共同种在(A)中可以获得(c1i)的额外收益,共同总在(B)中可以获得(c2i)的额外收益。

    (M)很快的算出了种植的最大收益,但是他想要考考你,你能回答他这个问题么?

    输入输出格式

    输入格式:

    第一行包括一个整数(n)

    第二行包括(n)个整数,表示(ai)第三行包括(n)个整数,表示(bi)第四行包括一个整数(m)接下来(m)行,

    对于接下来的第(i)行:第一个整数(ki),表示第(i)个作物组合中共有(ki)种作物,

    接下来两个整数(c1i)(c2i),接下来(ki)个整数,表示该组合中的作物编号。

    输出格式:

    只有一行,包括一个整数,表示最大收益

    输入输出样例

    输入样例:

    3
    4 2 1
    2 3 2
    1
    2 3 2 1 2
    

    输出样例:

    11
    

    (Solution)

    一开始看这个题我毫无头绪。这是网络流?不会建图啊。

    分析一下题目,有(n)个点,放在(A)里面有(ai)的收益,放在(B)里面有(bi)的收益。相当于从这个点到(A)(ai)的流,到(B)(bi)的流。于是求最大收益就是求总收益减去最小割,因为一个点只能放在一个集合里。

    那组合怎么办?

    显然组合内部的点要么都在一个集合里,要么这个点就没有贡献。也就是说,组合内部的点永远不能断开。我们建一个虚点,分别连接组合内部的点,边权是(INF),这样就永远不会断开。因为每个组合有三种情况:对(A)有贡献,对(B)有贡献,都没贡献,所以只建一个虚点是不够的,于是建两个。

    第一个链接(A)点,边权是(c1),然后和组合内部的点相连。第二个有组合内部的点连过来,然后指向(B)点,边权是(c2)

    这样图就建好了,然后跑最小割就行了。

    关于最小割

    首先,最大流等于最小割,证明脑补。

    也就是说,求最小割就是求最大流。

    思想比较简单,就是每次找一条还能增加流量的路径,增加流量,直到没有路径可以增加流量。

    每次(bfs)求最短的还有流量的路径,然后(dfs)求出能增加的流量,也就是这条路上残余流量最少的那条路的流量,答案加上这个数,顺便更新路径流量就好啦。

    (dinic):每次(bfs)的时候记录每个点到源点的最短路径条数,(dfs)按照最短路增广。

    详解见代码

    #include<iostream>
    #include<cstring>
    #include <cstdio>
    #include <queue>
    using namespace std;
    long long read(){
        long long x = 0;int f = 0;char c = getchar();
        while(c < '0' || c > '9') f |= c == '-',c = getchar();
        while(c >= '0' && c <= '9') x = (x << 1) + ( x << 3) + ( c ^ 48), c = getchar();
        return f ? -x: x;
    }
    
    const int INF = 2147483647;
    int n, m, a[1003], b[1003], ans, T, S;
    struct szh{
        int to, next, w;
    }e[5000005];
    int head[3003],cnt = 1;//1^1=0,2^1=3,4^1=5......
    void add(int x,int y,int z){
        e[++cnt].to = y,e[cnt].next = head[x],e[cnt].w = z,head[x] = cnt;
    }
    
    int dis[3003], fir[3003];
    bool bfs(){ //求是否有通路
        memset(dis, 0, sizeof(dis)); //每次清零
        queue<int> q;
        q.push(S), dis[S] = 1; 
        while(!q.empty()){
            int u = q.front(); q.pop();
            for (int i = head[u], v;v = e[i].to, i;i = e[i].next)
                if(e[i].w > 0 && !dis[v]) dis[v] = dis[u] + 1, q.push(v);
            	//如果当前边可以走,并且没有被搜到过,也就是说当前是最短路
        }
        return dis[T]; //看是否能到汇点
    }
    
    int dfs(int u, int flow){ //求通路最小流量
        if(u == T || !flow) return flow; //若到汇点就返回
        int sum = 0;
        for (int &i = fir[u], v;v = e[i].to, i;i = e[i].next)
            //取址是一个弧优化。因为每次增广,一个点可能被搜到多次,
            //而一个弧被走两次没有意义,所以取址直接删掉这一条弧
            if(e[i].w > 0 && dis[v] == dis[u] + 1){
            //如果当前边有流量且是最短路,更新
                int x = dfs(v, min(e[i].w, flow)); //x为当前通路的最小流量
                e[i].w -= x,e[i^1].w += x,sum += x; //更新
                if(!(flow -= x)) break; //如果不能再有更多的流量了,就退出
            }
        return sum;
    }
    
    void dinic(){
        while(bfs()){ //只要有通路,就可以进行增广
            for(int i = 0;i <= T;++ i) fir[i] = head[i];
            //因为后面会弧优化,所以另开一个数组代替
            ans -= dfs(S, INF);
        }
    }
    
    int main(){
        n = read();
        for (int i = 1;i <= n;++ i) ans += a[i] = read();
        for (int i = 1;i <= n;++ i) ans += b[i] = read();
        m = read();
        S = 0,T = (m+m+n+1);
        for (int i = 1;i <= n;++ i)
            add(S, i+m, a[i]),add(i+m, S, 0),add(i+m, T, b[i]),add(T, i+m,  0);
        for (int i = 1;i <= m;++ i){
            int k = read(),c1 = read(),c2 = read();
            while(k --){
                int x = read();
                add(i, x+m, INF),add(x+m, i, 0); //建图
                add(x+m, i+n+m, INF),add(i+m+n, x+m, 0);
            }
            add(S, i, c1),add(i, S, 0),add(i+n+m, T, c2),add(T, i+n+m, 0);
            ans += (c1 + c2);
        }
        dinic();
        printf("%d", ans);
        return 0;
    }
    
  • 相关阅读:
    docker 部署 禅道系统
    docker 部署 jenkins
    运筹方法
    软件工程基础知识
    操作系统知识
    程序设计语言基础知识
    计算机组成与配置
    oracle触发器
    性能测试监控工具的使用
    数据库设计范式
  • 原文地址:https://www.cnblogs.com/kylinbalck/p/10579807.html
Copyright © 2011-2022 走看看