题解 | 2023第二场多校

Link with Checksum

https://ac.nowcoder.com/acm/contest/57356/A


B题

知识点:最大权闭合子图(网络流)+ 树链剖分、线段树优化建图
感觉只要学一下最大权闭合子图是啥,会线段树优化就能会做了...
只是代码又长又臭,写晕了。还有网络流dinic一定要当前弧优化,不然tle....

闭合子图:原图中选一个点集,点集中所有点和它们的出边构成新图,称为闭合子图。(选一个点就必须选它的后继点)

最大权闭合子图求法:
源点和所有正权点连边,流量为这个正权;所有负权点和汇点连边,流量为负权的相反数;原图的边(u,v)uv连边,流量为无穷大。
最大权闭合子图的权值 = 所有正权 - 最大流
个人理解:流量从源点出发,到正权代表的点,到负权代表的点,再到汇点,这个过程可以理解为选择了正权点,然后正权点的贡献被负权点抵消了。
为啥不会选择负权大于正权。因为流量都要通过正权流出,正权流出多少流量,负权就只能接受这么多流量(流量守恒),所以负权大于正权时,正负抵消,相当于不选这个正权点,也就不会选这个负权点。
因此在网络流中,最大权闭合子图=它自己正负权相减,变成了,所有正权和 -(最大权闭合子图的负权 + 会带来负贡献的正权)     

在本题中,路线(只保留正收益视为上面的正权点,树上的边视为上面的负权点,然后跑网络流就搞定了。

但是n和m都有1e4,直接建图高达1e8的边.....
可以树链剖分,然后一条路线实际上就是一个区间,再用线段树将这个区间划分成 log 个区间,将路线和这些区间连边即可。
(感觉会做CF786B,也能很轻松理解这个这题的线段树优化)

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using PII = pair<int, int>;

#define lson (k << 1)
#define rson (k << 1 | 1)

const int N = 3 + 1e4;

int n, m;

namespace Dinic {
struct Edge {
    int ver, next, flow;
} E[N * 200];
int head[N * 200], cur[N * 200], id = 1;
int maxFlow;
int d[N * 200];
int nodeId; // 用于记录当前网络流,图中有多少个点
const int s = ++nodeId; // 源
const int t = ++nodeId; // 汇

void addEdge(int u, int v, int f) {
    E[++id] = {v, head[u], f};
    head[u] = id;
    E[++id] = {u, head[v], 0};
    head[v] = id;
}
int bfs() {
    queue<int> q;
    q.push(s);
    for (int i = 1; i <= nodeId; ++i) {
        d[i] = 0;
        cur[i] = head[i];
    }
    d[s] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        for (int i = cur[x]; i; i = E[i].next) {
            int y = E[i].ver;
            if (E[i].flow && !d[y]) {
                d[y] = d[x] + 1;
                q.push(y);
            }
        }
    }
    return d[t];
}
int dfs(int x, int f) {
    if (x == t || f == 0) {
        return f;
    }
    int flow = 0;
    for (int &i = cur[x]; i && f > flow; i = E[i].next) {
        int y = E[i].ver;
        if (d[y] != d[x] + 1) {
            continue;
        }
        int t = dfs(y, min(f - flow, E[i].flow));
        flow += t;
        E[i].flow -= t;
        E[i ^ 1].flow += t;
        if (f == flow) {
            return flow;
        }
    }
    return flow;
}
int run() {
    while (bfs()) {
        maxFlow += dfs(s, 1e9);
    }
    return maxFlow;
}
};  // namespace Dinic

namespace Tree {
struct E {
    int y, id; // 边(x,y),在网络流建图中点编号为id
};
vector<E> edge[N];
int sz[N], top[N], fa[N], son[N], dfn[N], d[N], id;
int p[N]; // 边(x,y),d[x]<d[y],id存到p[y]
int w[N]; // w[i]含义,p[x]的dfs序为i,w[i]就是p[x]

struct TreeNode {
    int l, r;
    int v; // [l,r]区间代表的点在网络流建图中的点的编号
} tr[N << 2];

void addEdge(int u, int v, int id) {
    edge[u].push_back({v, id});
    edge[v].push_back({u, id});
}

void dfs1(int x, int fa) {
    d[x] = d[fa] + 1;
    sz[x] = 1;
    Tree::fa[x] = fa;
    for (auto &[y, id] : edge[x]) {
        if (y == fa) {
            continue;
        }
        p[y] = id;
        dfs1(y, x);
        sz[x] += sz[y];
        if (sz[y] > sz[son[x]]) {
            son[x] = y;
        }
    }
}

void dfs2(int x, int fa, int topf) {
    dfn[x] = ++id;
    top[x] = topf;
    w[id] = p[x];
    if (son[x]) {
        dfs2(son[x], x, topf);
    }
    for (auto &[y, id] : edge[x]) {
        if (y == fa || y == son[x]) {
            continue;
        }
        dfs2(y, x, y);
    }
}

void segmentTreeBuild(int k, int l, int r, vector<int> ve) {
    tr[k].l = l;
    tr[k].r = r;
    tr[k].v = ++Dinic::nodeId;
    ve.push_back(tr[k].v);
    if (l == r) {
        if (r != 1) { // 1号点没有存边
            for (auto x : ve) { // 将线段树区间包含r的区间,和r连边
                Dinic::addEdge(x, w[r], 1e9);
            }
        }
        return;
    }
    int mid = l + r >> 1;
    segmentTreeBuild(lson, l, mid, ve);
    segmentTreeBuild(rson, mid + 1, r, ve);
}

void update(int k, int l, int r, int t) {
    if (l <= tr[k].l && tr[k].r <= r) {
        Dinic::addEdge(t, tr[k].v, 1e9);
        return;
    }
    int mid = tr[k].l + tr[k].r >> 1;
    if (l <= mid) {
        update(lson, l, r, t);
    }
    if (r > mid) {
        update(rson, l, r, t);
    }
}

void addDinicEdge(int u, int v, int f) {
    int t = ++Dinic::nodeId;
    Dinic::addEdge(Dinic::s, t, f);
    while (top[u] != top[v]) {
        if (d[top[u]] < d[top[v]]) {
            swap(u, v);
        }
        update(1, dfn[top[u]], dfn[u], t);
        u = fa[top[u]];
    }
    if (d[u] > d[v]) {
        swap(u, v);
    }
    if (dfn[u] + 1 <= dfn[v]) {
        // u这个存在边不在范围内,不能写成update(1, dfn[u], dfn[v], t);
        update(1, dfn[u] + 1, dfn[v], t);
    }
}
}  // namespace Tree

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> m;

    for (int i = 1; i < n; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        Tree::addEdge(u, v, ++Dinic::nodeId);
        Dinic::addEdge(Dinic::nodeId, Dinic::t, w);
    }
    Tree::dfs1(1, 0);
    Tree::dfs2(1, 0, 1);
    Tree::segmentTreeBuild(1, 1, n, {});

    int ans = 0;

    while (m--) {
        int s, t, a, b;
        cin >> s >> t >> a >> b;
        if (a - b > 0) {
            Tree::addDinicEdge(s, t, a - b);
            ans += a - b;
        }
    }

    ans -= Dinic::run();

    cout << ans << endl;

    return 0;
}


全部评论

相关推荐

咦哟,从去年八月份开始长跑,两处实习转正都失败了,风雨飘摇,终于拿到offer了更新一下面试记录:秋招:多部门反复面试然后挂掉然后复活,具体问了啥已经忘了,只是被反复煎炸,直至焦香😋春招:base北京抖音hr打来电话说再次复活,准备面试,gogogo北京抖音一面:六道笔试题:1.promise顺序2.定义域问题3.flat展开4.并发请求5.岛屿数量算法(力扣)深度,广度都写6.忘记了,好像也是算法,难度中等其他问题多是框架底层设计,实习项目重难点~~~秒过😇北京抖音二面:三道笔试题:(为什么只有三道是因为第三道没做出来,卡住了)1.中等难度算法(忘记啥题了,应该是个数组的)2.认识js的继承本质(手写继承模式,深入js的面相对象开发)3.手写vue的响应式(卡在了watch,导致挂掉)---后知后觉是我的注册副作用函数写得有问题,有点紧张了其他题目多是项目拷打,项目亮点,对实习项目的贡献~~~第二天,挂,but立马复活转战深圳客服当天约面深圳客服一面:六道笔试题,由于面过太多次字节,面试官叫我直接写,不用讲,快些写完😋,具体都是些继承,深拷贝(注意对数组对象分开处理,深层次对象,循环引用),加中等难度算法题~~~秒过深圳客服二面:口诉八股大战:大概囊括网络,浏览器渲染原理,动画优化,时间循环,任务队列等等(你能想到的简单八股通通拉出来鞭尸😋)算法题:笔试题6道:1:找出数组内重复的数,arr[0]-arr[n]内的数大小为[1-n],例如[1,2,2,3,3]返回[2,3],要求o(n),且不使用任何额外空间(做到了o(n),空间方面欠佳,给面试官说进入下一题,做不来了)2:原滋原味的继承(所以继承真滴很重要)3:力扣股票购买时机难度中等其他滴也忘记了,因为拿到offer后鼠鼠一下子就落地了,脑子自动过滤掉可能会攻击鼠鼠的记忆😷~~~秒过深圳客服三面:项目大战参与战斗的人员有:成员1:表单封装及其底层原理,使用成本的优化,声明式表单成员2:公司内部库生命周期管理成员3:第三方库和内部库冲突如何源码断点调试并打补丁解决成员4:埋点的艺术成员5:线上项目捷报频传如何查出内鬼成员6:大文件分片的风流趣事成员7:设计模式对对碰成员8:我构建hooks应对经理的新增的小需求的故事可能项目回答的比较流利,笔试题3道,都很简单,相信大家应该都可以手拿把掐😇~~~过过过无hr面后续煎熬等待几天直接hr打电话发offer了,希望大家也可以拿到自己心仪的offer
法力无边年:牛哇,你真是准备得充分,我对你没有嫉妒,都是实打实付出
查看19道真题和解析
点赞 评论 收藏
分享
缒梦&独舞:这家公司是这样的,去年给我实习offer了,不过也是面着玩儿的,他周六还要去做公益志愿活动
点赞 评论 收藏
分享
关于我大学本科四年,想了很多,但还是不知道该怎么动笔&nbsp;“大学四年,是我从懵懂少年走向职场青年的转折期。这一路跌跌撞撞,有迷茫,有遗憾,也有成长和决心。”&nbsp;大一刚进来时仍然有高中那股学习劲,经常一个人去图书馆学高等数学,但后面劲头一过便开始在宿舍开启躺平生活(现在想想那段时间真的很爽,无忧无虑)。由于大一担任班干部,所以经常要跟其他班的班干部交流,在此期间认识了隔壁班的一位女生,短发而很可爱,因为很多团建还有比赛都是我们两班一起参加的,而且我和她都是负责人,所以交集很多,后面慢慢地彼此对产生了好感,所以在大一刚开学的2个月后,我们在一起了,彼此之前都是初恋。但当时我真的是太太太直男了,对感情的想...
真烦好烦真烦:骗哥们可以,别把你自己也骗到了就行。哥们被你骗了真无所谓的,打个哈哈就过了。但希望你打完这段话后擦一下眼角,别让眼泪掉在手机屏幕上了就行。你说的这些话,哥们信一下也是没什么的。还能让你有个心里安慰,但这种话说出来骗骗兄弟就差不多得了,哥们信你一下也不会少块肉,但是你别搞得自己也当真了就行。哥们被你骗一下是真无所谓的,兄弟笑笑也就过去了。真不是哥们想要破你防,你擦擦眼泪好好想想,除了兄弟谁还会信你这些话?
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务