二者取一式问题

文章描述了一个基于网络流理论的集合划分问题,通过转化成最小割问题来寻找最大分值。在题目中,涉及到作物种植的收益,包括单个作物在不同区域的收益和特定作物组合的额外收益。通过构建网络流模型并应用Dinic算法求解最大收益。
摘要由CSDN通过智能技术生成

目录

一.描述

二.思路

三.题目


 一.描述:
二者取一式问题可以这样描述:将若干元素e1,e2,...en划分到两个集合A,B中。对于元素ei,它被划分到A或B中分别能获得一个
aei或bei的分值。除此之外,还给出若干个组合Ci属于A,当组合中的元素被同时划分到A或B时,可以获得额外的分值
求最大的分值。
 二.思路:
这个问题可以被转化为网络流中的最小割问题。如果我们把A作为源点,B作为汇点,那么这个网络的一个割就是一种划分方法。
1.如果没有组合的话,我们很容易就能建出这样的模型:


当我们去割它时,与A连通的点代表放到A集合中,与B连通的点代表放到B集合中。
当这个割是最小割时,剩下的边的容量和是最大的,故设最小割为cut,边权总和为sum,则所求最大分值为sum - cut。


2.现在我们考虑组合。
假设C1={e1,e2},且对应的额外值分别a1',a2'我们从A点伸出一条容量a1'的边通向X
现在我们的需求是:只有当1、2点都被归入A所在点集时,X才与A连通。
反过来想,当1被归入B所在点集时,要让A->X被割掉。
很自然地想到,让X连向1,这样当1被归入B所在点集时,A->X->1必然会断,否则A就与B连通了。
但如何确保割掉的是A->X而不是X->1呢?只要令X->1的容量为INF即可,无穷大边不会被割掉。2号点同理。
对于B号点,道理是一样的:


这时我们求最小割cut,并记非无穷边权和为sum,那么跟刚刚一样,sum - cut就是所求分数。
三.题目
1.洛谷P1361 小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个整数,表示该组合中的作物编号。
输出格式
只有一行,包括一个整数,表示最大收益
注意:
点集和边集的数组都要开大一点,但是也不要太大,否则清空数组会浪费不少时间。
其实很好计算,如果最多有m个组合,n个元素,那一共有m+n+2个点,最多有2mn+2n+m条边

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e6 + 10;             //数组需开大点,否则RE
const int INF = 0x3f3f3f3f;
int n, m, x, s, t,cnt=1;//s源点,t汇点
int head[maxn];
bool vis[maxn];
int lv[maxn], cur[maxn]; // lv是每个点的层数,cur用于当前弧优化标记增广起点
inline void read(int& a)   //快读
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch>'9'){
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9'){
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    a = s * w;
}
struct edge{
    int to, next, w;
    edge() {};
    edge(int _to, int _next, int _w) {
        to = _to, next = _next, w = _w;
    }
}e[maxn];
void add(int x, int y, int w){       //链式前向星
    e[++cnt] = edge(y, head[x], w);
    head[x] = cnt;
}
inline bool bfs(){ // BFS分层
    memset(lv, -1, sizeof(lv));
    lv[s] = 0;//源点深度为0
    memcpy(cur, head, sizeof(head)); // 当前弧优化初始化
    queue<int> q;     //不能直接更改head数组,head数组中含有反向0边
    q.push(s);
    while (!q.empty()){
        int p = q.front();
        q.pop();
        for (int eg = head[p]; eg; eg = e[eg].next){
            int to = e[eg].to, vol = e[eg].w;
            if (vol > 0 && lv[to] == -1)
                //若该残量不为0,且lv[to]还未分配深度,则给其分配深度并放入队列
                lv[to] = lv[p] + 1, q.push(to);
        }
    }
    return lv[t] != -1; // 如果汇点未访问过说明已经无法达到汇点,此时返回false
}
int dfs(int p = s, int flow =INF){
    if (p == t)
        return flow;
    int rmn = flow; // 剩余的流量
    for (int eg = cur[p]; eg && rmn; eg = e[eg].next){ // 如果已经没有剩余流量则退出
        cur[p] = eg; // 当前弧优化,更新当前弧
        int to = e[eg].to, vol = e[eg].w;
        if (vol > 0 && lv[to] == lv[p] + 1){ // 往层数高的方向增广
            int c = dfs(to, min(vol, rmn)); // 尽可能多地传递流量
            rmn -= c; // 剩余流量减少
            e[eg].w -= c; // 更新残余容量
            e[eg ^ 1].w += c; // 再次提醒,链式前向星的cnt需要初始化为1(或-1)才能这样求反向边
        }
    }
    return flow - rmn; // 返回传递出去的流量的大小
}
inline int dinic(){
    int ans = 0;
    while (bfs())
        ans += dfs();
    return ans;
}
//关键代码
int main()          //(**重点建图**)
{
    ios::sync_with_stdio(false);
    read(n), s = 0, t = n + 1;         
    int sum = 0, ans = t;
    for (int i = 1; i <= n; i++){ //与源点A相连的边
        read(x);
        add(0, i, x);
        add(i, 0, 0);         //反向0边,反悔机制
        sum += x;
    }
    for (int i = 1; i <= n; i++){ //与汇点B相连的边
        read(x);         
        add(i, n + 1, x);
        add(n + 1, 0, 0);
        sum += x;
    }
    read(m);
    for (int i = 0; i < m; ++i){   //考虑组合情况

        int k,c1,c2;
        read(k), read(c1), read(c2);
        add(s, ++ans, c1);       //增加组合Xi
        add(ans, s, 0);
        add(++ans, t, c2);//增加组合Yi
        add(t, ans, 0);
        sum += c1;
        sum += c2;
        while (k--){
            int c;
            read(c);
            add(ans - 1, c, INF); //设为无穷,确保割掉的是A->X而不是X->c
            add(c, ans - 1, 0);
            add(c, ans, INF);
            add(ans, c, 0);
        }
    }
    cout << sum - dinic();
    return 0;
}


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值