[XSY 1129] flow 最小割 树链剖分 线段树优化建图

题意

  给定一张 $N$ 个点的有根树, $1$ 为树的根.

  每个点有点权 $V[i]$ . 若 $V[i] > 0$ , 则可以获得 $V[i]$ 的收益; 若 $V[i] < 0$ , 则需要付出 $V[i]$ 的代价.

  如果选择了某个点 $x$ , 且它的连续的 $K$ 个祖先中存在一个没有选, 则需要付出 $P_x$ 的代价.

  最大化总收益.

分析

  将 树链剖分 + zkw线段树 嵌入网络中, 跑最小割.

实现

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;

#define F(i, a, b) for (register int i = (a); i <= (b); i++)

const int N = 20005;
const int INF = ~0u>>1;

int n, m;
struct Edge {
    int x, y, w; bool tag;
    friend inline bool operator < (Edge A, Edge B) { return A.w < B.w; }
}ed[100005];

int f[N];
int sum, srd[N];
vector<int> g[N];

int pre[N], dep[N], siz[N], son[N];
int top[N], tid[N], pid[N], num;

vector<Edge> cross[N];
int mn[70000], out[N];    // int m;
int id[N];

inline int rd(void) {
    int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1;
    int x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f;
}
inline void gmin(int &x, int y) { x = min(x, y); }

inline int Find(int x) { return f[x] == x ? x : f[x] = Find(f[x]); }

void Son(int x) {
    siz[x] = 1;
    for (vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++)
        if (pre[x] != *it) {
            pre[*it] = x, dep[*it] = dep[x]+1;
            Son(*it);
            siz[x] += siz[*it];
            if (siz[son[x]] < siz[*it]) son[x] = *it;
        }
}
void Split(int x, int anc) {
    top[x] = anc, pid[ tid[x] = ++num ] = x;
    if (son[x] > 0) Split(son[x], anc);
    for (vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++)
        if (pre[x] != *it && *it != son[x])
            Split(*it, *it);
}

inline int LCA(int x, int y) {
    while (top[x] != top[y])
        dep[top[x]] > dep[top[y]] ? x = pre[top[x]] : y = pre[top[y]];
    return dep[x] < dep[y] ? x : y;
}

inline void Min(int L, int R, int w) {
    for (L--, R++, L += (1<<m), R += (1<<m); L^R^1; L >>= 1, R >>= 1) {
        if (!(L&1)) gmin(mn[L^1], w);
        if (R&1) gmin(mn[R^1], w);
    }
}
inline void Fill(int x, int D, int w) {
    // dep >= D
    if (dep[x] < D) return;
    for (int t = top[x]; ; x = pre[t], t = top[x])
        if (dep[t] > D) Min(tid[t], tid[x], w);
        else {
            Min(tid[x] + (D - dep[x]), tid[x], w);
            //        ?        D
            //      tid[x]   dep[x]
            break;
        }
}
void Tsunami(int x, int L, int R, int w) {
    if (R < 1 || L > n) return;
    gmin(w, mn[x]);
    if (L == R) { out[pid[L]] = w; return; }
    int M = (L+R)>>1;
    Tsunami(x<<1, L, M, w);
    Tsunami(x<<1|1, M+1, R, w);
}

inline int Go(int x, int D) {
    // guaranteed that dep[x] >= D
    for (int t = top[x]; ; x = pre[t], t = top[x])
        if (D >= dep[t]) return pid[tid[x] + (D - dep[x])];    //同上
}

int main(void) {
    #ifndef ONLINE_JUDGE
        freopen("xsy2441.in", "r", stdin);
        freopen("xsy2441.out", "w", stdout);
    #endif

    for (int T = rd(), t = 1; t <= T; t++) {
        n = rd(), m = rd();
        F(i, 1, m) {
            int x = rd(), y = rd(), d = rd(), c = rd();
            ed[i] = (Edge){x, y, d * (1 - c), false};
        }

        sort(ed+1, ed+m+1);
        F(i, 1, n) f[i] = i, g[i].clear(); sum = 0, memset(srd, 0, sizeof srd);
        int c = 1;
        for (int i = 1; i <= m && c < n; i++) {
            int x = ed[i].x, y = ed[i].y, w = ed[i].w;
            int fx = Find(ed[i].x);
            int fy = Find(ed[i].y);
            if (fx != fy) {
                ed[i].tag = true, g[x].push_back(y), g[y].push_back(x);
                f[fx] = fy;
                sum += w, srd[x] += w, srd[y] += w, c++;
            }
        }

        memset(pre, 0, sizeof pre), memset(dep, 0, sizeof dep), memset(siz, 0, sizeof siz), memset(son, 0, sizeof son);
        memset(top, 0, sizeof top), memset(tid, 0, sizeof tid), memset(pid, 0, sizeof pid), num = 0;
        Son(1);
        Split(1, 1);

        F(i, 1, n) cross[i].clear();
        F(i, 1, m) if (!ed[i].tag) {
            int x = ed[i].x, y = ed[i].y, z = LCA(x, y);
            if (x != z && y != z)
                cross[z].push_back(ed[i]);
        }

        int tmp = m;
        for (m = 0; (1<<m) - 1 < n+1; m++);    //reuse m
        fill(mn+1, mn+(1<<m)+(1<<m), INF), fill(out+1, out+n+1, INF);
        F(i, 1, tmp) if (!ed[i].tag) {
            int x = ed[i].x, y = ed[i].y, w = ed[i].w, z = LCA(x, y);
            Fill(x, dep[z]+2, w);
            Fill(y, dep[z]+2, w);
        }
        Tsunami(1, 0, (1<<m)-1, INF);

        memset(id, 0, sizeof id);
        F(cut, 1, n) {
            int cnt = 0;
            for (vector<int>::iterator it = g[cut].begin(); it != g[cut].end(); it++)
                if (*it != pre[cut])
                    id[*it] = ++cnt;
            if (pre[cut] > 0) cnt++;
            num = 0;        // num, edge[] are reused.

            for (vector<Edge>::iterator it = cross[cut].begin(); it != cross[cut].end(); it++) {
                int x = it->x, y = it->y, w = it->w;
                int son_x = Go(x, dep[cut]+1);
                int son_y = Go(y, dep[cut]+1);
                ed[++num] = (Edge){id[son_x], id[son_y], w, false};
            }

            if (pre[cut] > 0)
                for (vector<int>::iterator it = g[cut].begin(); it != g[cut].end(); it++)
                    if (*it != pre[cut] && out[*it] != INF)
                        ed[++num] = (Edge){cnt, id[*it], out[*it], false};

            sort(ed+1, ed+num+1);
            F(i, 1, cnt) f[i] = i;
            int c = 1, s = 0;
            for (int i = 1; i <= num && c < cnt; i++) {
                int x = ed[i].x, y = ed[i].y, w = ed[i].w;
                int fx = Find(x);
                int fy = Find(y);
                if (fx != fy) {
                    f[fx] = fy;
                    s += w, c++;
                }
            }
            if (c < cnt) puts("inf"); else printf("%d\n", sum - srd[cut] + s);
        }
    }

    return 0;
}
时间: 2024-11-10 02:52:22

[XSY 1129] flow 最小割 树链剖分 线段树优化建图的相关文章

【CF725G】Messages on a Tree 树链剖分+线段树

[CF725G]Messages on a Tree 题意:给你一棵n+1个节点的树,0号节点是树根,在编号为1到n的节点上各有一只跳蚤,0号节点是跳蚤国王.现在一些跳蚤要给跳蚤国王发信息.具体的信息传输过程如下: 1.信息的发起者把信息上传给他父亲节点处的跳蚤,然后自身进入等待状态.3.跳蚤国王在收到信息时会将信息立刻下传到发来信息的那个儿子,跳蚤国王可以在同一时刻下传多份信息.4.上传:a把信息传给b.如果b正处于等待状态,则b会立刻将a发来的信息下传回去.如果同时有好多个信息传给b,则b会

[HDU3710] Battle Over Cities [树链剖分+线段树+并查集+kruskal+思维]

题面 一句话题意: 给定一张 N 个点, M 条边的无向连通图, 每条边上有边权 w . 求删去任意一个点后的最小生成树的边权之和. 思路 首先肯定要$kruskal$一下 考虑$MST$里面去掉一个点,得到一堆联通块,我们要做的就是用原图中剩下的边把这些联通块穿起来 考虑这个点$u$在$MST$上的位置,可以知道有两种边:一种是从$u$的任意一个儿子的子树连到$u$的子树外面的,一种是在$u$的两个儿子的子树之间连接的 第一种情况: 考虑边$(u,v)$,没有进入$MST$中,那么若它是某个节

Aizu 2450 Do use segment tree 树链剖分+线段树

Do use segment tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.bnuoj.com/v3/problem_show.php?pid=39566 Description Given a tree with n (1 ≤ n ≤ 200,000) nodes and a list of q (1 ≤ q ≤ 100,000) queries, process the queries in order and out

Hdu 3966 Aragorn&#39;s Story (树链剖分 + 线段树区间更新)

题目链接: Hdu 3966 Aragorn's Story 题目描述: 给出一个树,每个节点都有一个权值,有三种操作: 1:( I, i, j, x ) 从i到j的路径上经过的节点全部都加上x: 2:( D, i, j, x ) 从i到j的路径上经过的节点全部都减去x: 3:(Q, x) 查询节点x的权值为多少? 解题思路: 可以用树链剖分对节点进行hash,然后用线段树维护(修改,查询),数据范围比较大,要对线段树进行区间更新 1 #include <cstdio> 2 #include

【bzoj3589】动态树 树链剖分+线段树

题目描述 别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件 事件0:这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子. 事件1:小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和. 注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次. 输入 第一行一个整数n(1<=n<=200,000), 即节点数. 接下来n-1行, 每行两个数字u,

BZOJ2243 (树链剖分+线段树)

Problem 染色(BZOJ2243) 题目大意 给定一颗树,每个节点上有一种颜色. 要求支持两种操作: 操作1:将a->b上所有点染成一种颜色. 操作2:询问a->b上的颜色段数量. 解题分析 树链剖分+线段树. 开一个记录类型,记录某一段区间的信息.l 表示区间最左侧的颜色 , r 表示区间最右侧的颜色 , sum 表示区间中颜色段数量. 合并时判断一下左区间的右端点和有区间的左端点的颜色是否一样. 树上合并时需要用两个变量ans1,ans2来存储.ans1表示x往上走时形成的链的信息,

bzoj4304 (树链剖分+线段树)

Problem T2 (bzoj4304 HAOI2015) 题目大意 给定一颗树,1为根节点,要求支持三种操作. 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a . 操作 3 :询问某个节点 x 到根的路径中所有点的点权和. 解题分析 练手题.树链剖分+线段树. 参考程序 1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #incl

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要

【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分+线段树区间合并

题目描述 由乃正在做她的OJ.现在她在处理OJ上的用户排名问题.OJ上注册了n个用户,编号为1-",一开始他们按照编号 排名.由乃会按照心情对这些用户做以下四种操作,修改用户的排名和编号:然而由乃心情非常不好,因为Deus天 天问她题...因为Deus天天问由乃OI题,所以由乃去学习了一下OI,由于由乃智商挺高,所以OI学的特别熟练她 在RBOI2016中以第一名的成绩进入省队,参加了NOI2016获得了金牌保送 Deus:这个题怎么做呀? yuno:这个不是NOI2014的水题吗... Deu

HDU 2460 Network(双连通+树链剖分+线段树)

HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链剖分+线段树处理 代码: #include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; #pragma comment(linke