题解 洛谷 P3410 拍照

题目链接

题目分析

如果我们将要求拍照的人和下属之间按照要求关系连有向边,我们就会发现题目实际上是要求我们求出有向图的最大权闭合子图。即,在一张有向图上选择一个点集,要求点集中的每一个点的后继也都在这个点集中,使点集中点权和最大。

对于这类问题,有一个结论:从源点向所有正权点连边,边权为原点权;从所有负权点向汇点连边,边权为原点权的绝对值;原图中的边权设为正无穷。则最大权闭合子图的权值和 = 所有正权点的权值和 - 新图的最小割。

对结论的证明

我们先做如下约定:

  1. 割掉源点与正权点 \(u\) 之间的边,表示不选择点 \(u\) 进入子图。
  2. 割掉负权点 \(v\) 与汇点之间的边,表示选择点 \(v\) 进入子图。

则我们跑一遍最小割后得到的一定是闭合子图,证明如下:考虑任意得到的子图内的任意正权点 \(u\),若存在其后继节点 \(v\) 没有被选择进入子图,分为两种情况:如果 \(v\) 权值为负,按照上述约定,点 \(v\) 与汇点间的边会被保留,源点与汇点联通,与最小割的定义矛盾;如果 \(v\) 权值为正,按照上述约定,源点与点 \(v\) 间的边会被割掉,但点 \(u\) 与点 \(v\) 联通,不割掉源点与点 \(v\) 间的边仍然是一个割,且权值和更小,与最小割的定义矛盾。

因为最大权闭合子图的权值和为:

被选的正权点权值和 - 被选的负权点权值和的绝对值

等价于

所有正权点的权值和 - (不被选的正权点的权值和 + 被选的负权点权值和的绝对值)

而根据先前的约定,我们可以得到:

最小割 = 不被选的正权点的权值和 + 被选的负权点权值和的绝对值

所以

最大权闭合子图的权值和 = 所有正权点的权值和 - 新图的最小割

代码实现

因为最大流=最小割,所以跑一边最大流即可。笔者在这里选择使用 Dinic 算法来实现代码。

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 200 + 5;
const int MAXM = 1000000 + 5;
const int INF = INT_MAX;

queue<int> q;
int head[MAXN], to[MAXM], nxt[MAXM], flow[MAXM], cap[MAXM];
int tot = 1;
int dep[MAXN], cur[MAXN];
int m, n, s, t;

void addEdge(int u, int v, int c, int f) {
    tot++;
    to[tot] = v;
    nxt[tot] = head[u];
    flow[tot] = f;
    cap[tot] = c;
    head[u] = tot;
}

bool bfs() {
    memset(dep, 0, sizeof(dep));
    while(!q.empty()) q.pop();
    q.push(s);
    dep[s] = 1;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = head[u]; i != 0; i = nxt[i]) {
            if(cap[i] > flow[i] && dep[to[i]] == 0) {
                dep[to[i]] = dep[u] + 1;
                q.push(to[i]);
                if(to[i] == t) return true;
            }
        }
    }
    return false;
}

int dinic(int u, int f) {
    if(u == t || f == 0) {
        return f;
    }
    int rest = f;
    for(int& i = cur[u]; i != 0; i = nxt[i]) {
        if(cap[i] > flow[i] && dep[to[i]] == dep[u] + 1) {
            int k = dinic(to[i], min(rest, cap[i] - flow[i]));
            if(k == 0) {
                dep[to[i]] = 0;
            }
            flow[i] += k;
            flow[i ^ 1] -= k;
            rest -= k;
            if(rest == 0) break;
        }
    }
    return f - rest;
}

int main() {
    scanf("%d%d", &m, &n);
    s = m + n + 1;
    t = m + n + 2;
    int sum = 0;
    for(int i = 1; i <= m; i++) {
        int v, p;

        scanf("%d", &v);
        sum += v;
        addEdge(s, i, v, 0);
        addEdge(i, s, 0, 0);
        while(1) {
            scanf("%d", &p);
            if(p == 0) break;
            addEdge(i, p + m, INF, 0);
            addEdge(p + m, i, 0, 0);
        }
    }
    for(int i = 1; i <= n; i++) {
        int v;

        scanf("%d", &v);
        addEdge(i + m, t, v, 0);
        addEdge(t, i + m, 0, 0);
    }
    int maxflow = 0;
    while(bfs()) {
        memcpy(cur, head, sizeof(head));
        maxflow += dinic(s, INF);
    }
    printf("%d\n", sum - maxflow);

    return 0;
}

posted @ 2022-01-24 20:50  智子酱  阅读(51)  评论(0编辑  收藏  举报