[ARC107F] Sum of Abs

题意

给定一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 个点有两个点权 \(A_i\)\(B_i\);删去第 \(i\) 个点和与之相连的边的代价为 \(A_i\)。定义一个极大连通块的权值为连通块中点的 \(B\)​ 权值和的绝对值。最大化删除若干点后,所有极大连通块权值和减去总代价。

\(1\le n,m\le 300\)\(1\le A_i\le 10^6\)\(0\le |B_i|\le 10^6\)

思路

考虑每个点对答案的贡献。对于第 \(i\) 个点,显然如果它被删除则有 \(-a_i\) 的贡献。如果它最终被归入权值和为正的连通块中则有 \(b_i\) 的贡献;反之则有 \(-b_i\) 的贡献。不考虑是否合法,理想的最优答案显然是将所有 \(b_i\) 为正的点丢到同一个连通块(下文中称为”正连通块“)中,所有 \(b_i\) 为负的点丢到另一个连通块(下文中称为”负连通块“)中。

现在考虑使答案变得合法所需要的代价。对于第 \(i\) 个点,如果它被删去则有 \(a_i+|b_i|\) 的代价,前者为删除代价,后者为从合法答案中去除的部分;如果 \(b_i\) 为正却被归入了负连通块中,那么代价为 \(b_i\times 2\),分别为正连通块中缺少的部分和负连通块中 \(b_i>0\) 导致减少的部分;如果 \(b_i\) 为负却被归入了正连通块中,那么同理代价为 \((-b_i)\times 2\)。而对于同一个连通块中的点,它们最终的贡献取正取负显然应该是相同的。

于是我们考虑构建网络流中的最小割。建立超级源点 \(S\) 和超级汇点 \(T\),令最终 \(b_i\) 取正的点有连边 \(S\to i\),反之有连边 \(i\to T\)。套路地,对于点 \(u\),我们将其拆成入点 \(u\) 和出点 \(u'\),则删除 \(u\) 就可以表示为删除 \(u\to u'\) 这条边,故该边边权即为代价 \(a_u+|b_u|\)。对于原图中的边 \((u,v)\),我们连两条容量无穷大的边 \(u'\to v,v'\to u\) 以防通过删边不删点获得错误的答案。

最后按照上述的代价进行建边即可。最终答案即为理想答案减去最小割。

实现

可以把超级源点设为 \(0\),超级汇点设为 \(2n+1\);入点和出点的编号分别设为 \(2i-1\)\(2i\),最小割即最大流采用 Dinic 算法,建边时每建一条有向边就紧接着建一条容量为 \(0\) 的反向边。时间复杂度上限为 \(O(n^2m)\),实际要快得多。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 305, maxm = 305;
int head[maxn << 1], cnt = 1;
struct Edge {
    int to,nxt,val;
    Edge (int x = 0,int y = 0,int z = 0) {
        to = x, nxt = y, val = z;
    }
} e[(maxm << 2) + (maxn << 2)];
void addEdge(int u,int v,int w) {
    e[++ cnt] = Edge(v,head[u],w);
    head[u] = cnt;
}
int dep[maxn << 1], S, T; queue<int> q;
const int inf = 0x3f3f3f3f;
int cur[maxn << 1];
bool bfs() {
    for (int i = S;i <= T;i ++) cur[i] = head[i];
    memset(dep,0x3f,sizeof(dep));
    dep[S] = 0; q.push(S);
    while (q.size()) {
        int u = q.front(); q.pop();
        for (int i = head[u];i;i = e[i].nxt) {
            int v = e[i].to, w = e[i].val;
            if (w == 0 || dep[v] < inf) continue;
            dep[v] = dep[u] + 1; q.push(v);
        }
    } return dep[T] < inf;
}
int dfs(int u,int lim) {
    if (lim == 0 || u == T) return lim;
    int flow = 0;
    for (int i = cur[u];i;i = e[i].nxt) {
        int v = e[i].to, w = e[i].val, tmp;
        cur[u] = i;
        if (dep[v] == dep[u] + 1 && (tmp = dfs(v,min(lim,w)))) {
            flow += tmp, lim -= tmp, e[i].val -= tmp, e[i ^ 1].val += tmp;
            if (lim == 0) break;
        }
    } return flow;
}
int Dinic() {
    int ans = 0;
    while (bfs()) ans += dfs(S,inf);
    return ans;
}
#define abs(x) ((x) > (0) ? (x) : (-(x)))
#define in(x) ((x) << (1))
#define out(x) (((x) << (1)) - (1))
int n, m;
int A[maxn], B[maxn];
int main() {
    scanf("%d%d",&n,&m), S = 0, T = (n << 1) + 1;
    int sum = 0;
    for (int i = 1;i <= n;i ++) scanf("%d",&A[i]);
    for (int i = 1;i <= n;i ++) scanf("%d",&B[i]), sum += abs(B[i]);
    for (int i = 1;i <= n;i ++) {
        addEdge(in(i), out(i), A[i] + abs(B[i])), addEdge(out(i), in(i), 0);
        if (B[i] < 0) addEdge(S, in(i), -1 * (B[i] << 1)), addEdge(in(i), S, 0);
        else addEdge(out(i), T, B[i] << 1), addEdge(T, out(i), 0);
    }
    for (int i = 1, u, v;i <= m;i ++) {
        scanf("%d%d",&u,&v);
        addEdge(out(u), in(v), inf), addEdge(in(v), out(u), 0);
        addEdge(out(v), in(u), inf), addEdge(in(u), out(v), 0);
    } return printf("%d", sum - Dinic()), 0;
}
posted @ 2024-08-17 06:50  underthetime  阅读(30)  评论(0)    收藏  举报