标签: LCT

[NOI2014]魔法森林 题解

[NOI2014]魔法森林 题解

题目地址:洛谷:【P2387】[NOI2014]魔法森林 – 洛谷、BZOJ:Problem 3669. — [Noi2014]魔法森林

题目描述

为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士。魔法森林可以被看成一个包含 n 个节点 m 条边的无向图,节点标号为 1,2,3,…,n,边标号为 1,2,3,…,m。初始时小 E 同学在 1 号节点,隐士则住在 n 号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪 就会对其发起攻击。幸运的是,在 1 号节点住着两种守护精灵:A 型守护精灵与 B 型守护精灵。小 E 可以借助它们的力量,达到自己的目的。
只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无 向图中的每一条边 ei 包含两个权值 ai 与 bi 。若身上携带的 A 型守护精灵个数不 少于 ai ,且 B 型守护精灵个数不少于 bi ,这条边上的妖怪就不会对通过这条边 的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向 小 E 发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到 隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A 型守护精灵的 个数与 B 型守护精灵的个数之和。

输入输出格式

输入格式:
输入文件的第 1 行包含两个整数 n,m,表示无向图共有 n 个节点,m 条边。 接下来 m 行,第i+ 1 行包含 4 个正整数 Xi,Yi,ai,bi,描述第i条无向边。 其中Xi与 Yi为该边两个端点的标号,ai 与 bi 的含义如题所述。 注意数据中可能包含重边与自环。

输出格式:
输出一行一个整数:如果小 E 可以成功拜访到隐士,输出小 E 最少需要携 带的守护精灵的总个数;如果无论如何小 E 都无法拜访到隐士,输出“-1”(不含引号)。

输入输出样例

输入样例#1:

4 5 
1 2 19 1 
2 3 8 12 
2 4 12 15 
1 3 17 8 
3 4 1 17 

输出样例#1:

32

输入样例#2:

3 1 
1 2 1 1 

输出样例#2:

-1

说明

2<=n<=50000, 0<=m<=100000

题解

本题可以使用LCT维护最小生成树动态加边的办法。一条边有两个边权,不好处理,我们将所有的边按照一个边权a排序,依次插入,并每次都用当前答案更新答案。遇到该边的两个点已是连通状态,如果这条路径上存在比当前边的边权b还大的边,就删去路径上的该边,否则不插入这条边。我们可以把边权放在点上,插入一条边变成使边的两端点向代表边的点连接。

代码

// Code by KSkun, 2018/4
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 150005, INF = 1e9;

struct Node {
    int val, mx, ch[2], fa;
    bool rev;
} tr[MAXN];

inline bool isleft(int p) {
    return tr[tr[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    int fa = tr[p].fa;
    return tr[fa].ch[0] != p && tr[fa].ch[1] != p;
} 

inline void update(int p) {
    int lch = tr[p].ch[0], rch = tr[p].ch[1];
    tr[p].mx = p;
    if(tr[tr[lch].mx].val > tr[tr[p].mx].val) tr[p].mx = tr[lch].mx;
    if(tr[tr[rch].mx].val > tr[tr[p].mx].val) tr[p].mx = tr[rch].mx;
}

inline void reverse(int p) {
    std::swap(tr[p].ch[0], tr[p].ch[1]);
    tr[p].rev ^= 1;
}

inline void pushdown(int p) {
    int lch = tr[p].ch[0], rch = tr[p].ch[1];
    if(tr[p].rev) {
        if(lch) reverse(lch);
        if(rch) reverse(rch);
        tr[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = tr[p].fa;
    }
    sta[stop++] = p;
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
    tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
    tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
    tr[fa].fa = p; tr[p].ch[!t] = fa;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
        if(!isroot(fa)) rotate(isleft(p) == isleft(fa) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(int q = 0; p; q = p, p = tr[p].fa) {
        splay(p);
        tr[p].ch[1] = q;
        update(p);
    }
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(tr[p].ch[0]) p = tr[p].ch[0];
    return p;
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    tr[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(tr[v].ch[0] != u || tr[u].ch[1]) return;
    tr[v].ch[0] = tr[u].fa = 0;
}

inline int query(int u, int v) {
    split(u, v);
    return tr[v].mx;
}

int n, m, ans = INF;

inline void updateans(int a) {
    if(findrt(1) == findrt(n)) {
        ans = std::min(ans, a + tr[query(1, n)].val);
    }
}

struct Edge {
    int u, v, a, b;
    inline bool operator<(const Edge &rhs) const {
        return a < rhs.a;
    }
} edge[MAXN];

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= m; i++) {
        edge[i].u = readint(); edge[i].v = readint();
        edge[i].a = readint(); edge[i].b = readint();
    }
    std::sort(edge + 1, edge + m + 1);
    for(int i = 1; i <= m; i++) {
        int u = edge[i].u, v = edge[i].v, a = edge[i].a, b = edge[i].b, 
            urt = findrt(u), vrt = findrt(v);
        if(urt == vrt) { // alrealy connected
            int t = query(u, v);
            if(tr[t].val > b) {
                cut(t, edge[t - n].u);
                cut(t, edge[t - n].v);
            } else {
                updateans(a);
                continue;
            }
        } 
        tr[i + n].val = b; tr[i + n].mx = i + n;
        link(u, i + n); link(v, i + n);
        updateans(a);
    }
    printf("%d", ans != INF ? ans : -1);
    return 0;
}
[CC-MONOPLOY]Gangsters of Treeland 题解

[CC-MONOPLOY]Gangsters of Treeland 题解

题目地址:CodeChef:Gangsters of Treeland | CodeChef

题目描述

树之大陆是一个有N座城市的王国(城市从0开始标号)。城市之间有N-1条道路链接,使得两点之间恰好有一条道路(也就是说,形成一棵树形结构)。城市0是首都。
初始时,每个城市都被一个帮会所控制。村民在相邻的城市间移动,如果这两个城市不是隶属于同一个帮会的势力范围内,那么需要支付一个单位的代价。
每一年都会有新的帮会涌入首都,他们会扩张自己的势力范围。具体说来,他们会占据从首都到u路径上的所有城市(包括首都和u)。因为这个原因,来往于城市间的代价变得琢磨不定,这让村民们很苦恼。于是他们找你来帮忙。
给定一个城市u,定义f(u)为以u为根的子树中所有结点到根结点的代价的平均值。

输入输出格式

输入格式:
第一行一个整数T表示数据组数。接下来有T组数据,每组数据的第一行有一个整数N表示城市的数目。接下来的N-1行,每行有两个用空格隔开的整数Ai、Bi表示一条连接这两点的边。接下来的一行一个整数Q,表示接下来有Q组询问,每个询问包含一个字符t和一个整数u。
如果t = ‘O’,表示一个新的帮会占据了从首都到u路径上的城市。
如果t = ‘q’,表示询问f(u)。

输出格式:
对每一组询问,输出一行表示对应的答案。

输入输出样例

输入样例#1:

1
13
0 1
0 2
1 11
1 10
1 9
9 12
2 5
5 8
2 4
2 3
4 6
4 7
7
q 0
O 4
q 6
q 2
O 9
q 9
q 2

输出样例#1:

2.0000000000
1.0000000000
0.8571428571
0.5000000000
1.8571428571

说明

1≤T≤15
1≤N≤10^5
1≤Q≤10^5
数据保证所有N的总和不超过2 * 10^5。
数据保证所有Q的总和不超过2 * 10^5。

题解

我们想到,O操作其实很像LCT的access操作。我们是不是可以考虑用LCT的access来做这个题。
对于询问,我们可以考虑求和再除,求和又因为树的形态确定可以通过DFS序求子树和,可以用线段树处理。考虑一次染色对答案的影响。假如access的过程中遇到一个轻边变重边的转折点,那么这个原来重边连接的子树的答案会+1,而现在重边连接的子树答案会-1。在access的操作过程中维护这个即可。
DFS序和子树大小可以一个DFS解决,由于一开始没有相同颜色的点,我们可以直接在LCT上标记所有边都是轻边。
这次的代码写的有点乱qwq。

代码

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <algorithm>
#include <vector>

typedef long long LL; 

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isop(char c) {
    return c == 'O' || c == 'q';
}

inline char readop() {
    register char c;
    while(!isop(c = fgc()));
    return c;
}

const int MAXN = 100005, INF = 1e9;
int T, n, q, ut, vt;
char op;

std::vector<int> gra[MAXN];

int dfn[MAXN], ptn[MAXN], siz[MAXN], dep[MAXN], tot;
bool vis[MAXN];

inline void graphinit() {
    for(int i = 1; i <= n; i++) gra[i].clear();
    tot = 0;
    memset(vis, 0, sizeof(vis));
}

#define lch o << 1
#define rch (o << 1) | 1
#define mid ((l + r) >> 1)

struct SGTNode {
    LL val, add;
} sgt[MAXN << 2];

inline void sgtinit(int o, int l, int r) {
    sgt[o].add = 0;
    if(l == r) {
        sgt[o].val = dep[ptn[l]];
        return;
    }
    sgtinit(lch, l, mid);
    sgtinit(rch, mid + 1, r);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline void pushdown(int o, int l, int r) {
    if(sgt[o].add) {
        sgt[lch].add += sgt[o].add;
        sgt[rch].add += sgt[o].add;
        sgt[lch].val += sgt[o].add * (mid - l + 1);
        sgt[rch].val += sgt[o].add * (r - mid);
        sgt[o].add = 0;
    }
}

inline void modify(int o, int l, int r, int ll, int rr, LL v) {
    if(l >= ll && r <= rr) {
        sgt[o].add += v;
        sgt[o].val += v * (r - l + 1);
        return;
    }
    pushdown(o, l, r);
    if(ll <= mid) modify(lch, l, mid, ll, rr, v);
    if(rr > mid) modify(rch, mid + 1, r, ll, rr, v);
    sgt[o].val = sgt[lch].val + sgt[rch].val;
}

inline LL query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) {
        return sgt[o].val;
    }
    pushdown(o, l, r);
    LL res = 0;
    if(ll <= mid) res += query(lch, l, mid, ll, rr);
    if(rr > mid) res += query(rch, mid + 1, r, ll, rr);
    return res;
}

#undef lch
#undef rch
#undef mid

struct LCTNode {
    int ch[2], fa;
} lct[MAXN];

inline bool isleft(int p) {
    return lct[lct[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    register int fa = lct[p].fa;
    return lct[fa].ch[0] != p && lct[fa].ch[1] != p;
}

inline void rotate(int p) {
    register bool t = !isleft(p); register int fa = lct[p].fa, ffa = lct[fa].fa;
    lct[p].fa = ffa; if(!isroot(fa)) lct[ffa].ch[!isleft(fa)] = p;
    lct[fa].ch[t] = lct[p].ch[!t]; lct[lct[fa].ch[t]].fa = fa;
    lct[p].ch[!t] = fa; lct[fa].fa = p;
}

inline void splay(int p) {
    for(register int fa = lct[p].fa; !isroot(p); rotate(p), fa = lct[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
}

inline int getsubrt(int p) {
    while(lct[p].ch[0]) p = lct[p].ch[0];
    return p;
}

inline void access(int p) {
    for(register int q = 0; p; lct[p].ch[1] = q, q = p, p = lct[p].fa) {
        splay(p);
        if(lct[p].ch[1]) {
            int ch = getsubrt(lct[p].ch[1]);
            modify(1, 1, n, dfn[ch], dfn[ch] + siz[ch] - 1, 1);
        }
        if(q) {
            int ch = getsubrt(q);
            modify(1, 1, n, dfn[ch], dfn[ch] + siz[ch] - 1, -1);
        }
    }
}

inline void dfs(int u) {
    vis[u] = true;
    dfn[u] = ++tot;
    ptn[tot] = u;
    siz[u] = 1;
    for(int v : gra[u]) {
        if(vis[v]) continue;
        dep[v] = dep[u] + 1;
        lct[v].fa = u;
        dfs(v);
        siz[u] += siz[v];
    }
}

inline void lctinit() {
    memset(lct, 0, sizeof(lct));
}

int main() {
    T = readint();
    while(T--) {
        n = readint();
        graphinit();
        lctinit();
        for(int i = 1; i < n; i++) {
            ut = readint() + 1; vt = readint() + 1;
            gra[ut].push_back(vt);
            gra[vt].push_back(ut);
        }
        dfs(1);
        sgtinit(1, 1, n);
        q = readint();
        while(q--) {
            op = readop();
            ut = readint() + 1;
            if(op == 'O') {
                access(ut);
            } else {
                printf("%.8lf\n", query(1, 1, n, dfn[ut], dfn[ut] + siz[ut] - 1) / double(siz[ut]));
            }
        }
    }
    return 0;
}
[CC-GERALD07]Chef and Graph Queries 题解

[CC-GERALD07]Chef and Graph Queries 题解

题目地址:CodeChef:Chef and Graph Queries | CodeChef

题目描述

大厨有一个无向图G。顶点从1到N标号,边从1到M标号。
大厨有Q对询问Li,Ri(1≤Li≤Ri≤M)。对于每对询问,大厨想知道当仅保留编号X满足Li≤X≤Ri所在的边时,图G中有多少连通块。
请帮助大厨回答这些询问。

输入输出格式

输入格式:
输入数据的第一行包含一个整数T,表示数据组数。对于每组数据,第一行包含三个整数N,M,Q。接下来的M行,每行包含两个整数Vi,Ui,依次表示一条无向边。接下来的Q行,每行包含两个整数Li,Ri,依次表示一组询问。

输出格式:
对于每组询问,输出对应的连通块数。

输入输出样例

输入样例#1:

2
3 5 4
1 3
1 2
2 1
3 2
2 2
2 3
1 5
5 5
1 2
1 1 1
1 1
1 1

输出样例#1:

2
1
3
1
1

说明

  • 1≤T≤1000
  • 1≤N, M, Q≤200000
  • 1≤Ui, Vi≤N
  • 1≤Li≤Ri≤M
  • 数据保证所有N的和不超过200000。所有M的和不超过200000。所有Q的和不超过200000。
  • 注意数据可能包含自环和重边!

题解

我们考虑一条边会对连通块产生什么影响,加一条边会合并某两个连通块,使得答案减少1,当然这仅当加这条边不会成环以及没有重边和自环的时候才会产生影响。因此我们考虑维护一个生成树,使得每加一条边,如果成环,删去该环中编号最小的边。该生成树上编号为[Li, Ri]的边有多少条,答案就是n-几。我们可以在逐条加边的过程中动态维护生成树以及生成树上的边编号计数。动态维护生成树可以用LCT做,而边编号计数则使用权值线段树。
这道题目的强制在线升级版可以看[BZOJ3514]Codechef MARCH14 GERALD07加强版 题解 | KSkun’s Blog
LCT和线段树都是常数大的东西,还好跑过了。

代码

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <algorithm>

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 400005, INF = 1e9;

struct LCTNode {
    int ch[2], fa, val, mn;
    bool rev;
} lct[MAXN];

inline bool isleft(int p) {
    return lct[lct[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    register int fa = lct[p].fa;
    return lct[fa].ch[0] != p && lct[fa].ch[1] != p;
}

inline void update(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    lct[p].mn = p;
    if(lct[lct[ls].mn].val < lct[lct[p].mn].val) lct[p].mn = lct[ls].mn;
    if(lct[lct[rs].mn].val < lct[lct[p].mn].val) lct[p].mn = lct[rs].mn;
}

inline void reverse(int p) {
    std::swap(lct[p].ch[0], lct[p].ch[1]);
    lct[p].rev ^= 1;
}

inline void pushdown(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    if(lct[p].rev) {
        if(ls) reverse(ls);
        if(rs) reverse(rs);
        lct[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = lct[p].fa;
    }
    pushdown(p);
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    register bool t = !isleft(p); register int fa = lct[p].fa, ffa = lct[fa].fa;
    lct[p].fa = ffa; if(!isroot(fa)) lct[ffa].ch[!isleft(fa)] = p;
    lct[fa].ch[t] = lct[p].ch[!t]; lct[lct[fa].ch[t]].fa = fa;
    lct[p].ch[!t] = fa; lct[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(register int fa = lct[p].fa; !isroot(p); rotate(p), fa = lct[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(register int q = 0; p; q = p, p = lct[p].fa) {
        splay(p);
        lct[p].ch[1] = q;
        update(p);
    }
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(lct[p].ch[0]) p = lct[p].ch[0];
    return p;
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    lct[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(lct[v].ch[0] != u || lct[lct[v].ch[0]].ch[1]) return;
    lct[u].fa = lct[v].ch[0] = 0;
}

inline int query(int u, int v) {
    split(u, v);
    return lct[v].mn;
}

int sgt[MAXN << 2];

inline void insert(int o, int l, int r, int x, int v) {
    if(l == r) {
        sgt[o] += v;
        return;
    }
    register int mid = (l + r) >> 1;
    if(x <= mid) insert(o << 1, l, mid, x, v);
    else insert((o << 1) | 1, mid + 1, r, x, v);
    sgt[o] = sgt[o << 1] + sgt[(o << 1) | 1];
}

inline int query(int o, int l, int r, int ll, int rr) {
    if(l >= ll && r <= rr) return sgt[o];
    register int mid = (l + r) >> 1, res = 0;
    if(ll <= mid) res += query(o << 1, l, mid, ll, rr);
    if(rr > mid) res += query((o << 1) | 1, mid + 1, r, ll, rr);
    return res;
}

int T, n, m, q, ut, vt, lst[MAXN], u[MAXN], v[MAXN], ans[MAXN];

struct Edge {
    int u, v;
} edge[MAXN];

struct Query {
    int l, r, id;
    inline bool operator<(const Query &rhs) const {
        return r < rhs.r;
    }
} quer[MAXN];

int main() {
    T = readint();
    while(T--) {
        n = readint(); m = readint(); q = readint();
        // init
        for(int i = 0; i <= n + m; i++) {
            lct[i].ch[0] = lct[i].ch[1] = lct[i].fa = lct[i].mn = lct[i].rev = 0;
            lct[i].val = INF;
        }
        memset(sgt, 0, sizeof(sgt));
        // read
        for(int i = 1; i <= m; i++) {
            edge[i].u = readint(); edge[i].v = readint(); 
        }
        for(int i = 1; i <= q; i++) {
            quer[i].l = readint(); quer[i].r = readint(); quer[i].id = i;
        }
        std::sort(quer + 1, quer + q + 1);
        // work
        int tot = n, ptr = 1;
        for(int i = 1; i <= m; i++) {
            int u = edge[i].u, v = edge[i].v;
            if(u != v && findrt(u) == findrt(v)) {
                int t = query(u, v), tv = lct[t].val;
                cut(edge[tv].u, t);
                cut(edge[tv].v, t);
                insert(1, 1, m, tv, -1);
            }
            if(u != v) {
                tot++;
                lct[tot].mn = tot;
                lct[tot].val = i;
                link(u, tot);
                link(v, tot);
                insert(1, 1, m, i, 1);
            }
            while(ptr <= q && quer[ptr].r <= i) {
                if(quer[ptr].r == i) {
                    ans[quer[ptr].id] = n - query(1, 1, m, quer[ptr].l, quer[ptr].r);
                }
                ptr++;
            }
        }
        for(int i = 1; i <= q; i++) {
            printf("%d\n", ans[i]);
        }
    }
    return 0;
}