CF487E Tourists 圆方树、树链剖分

传送门



注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点

注意:这里不能缩边双联通分量,样例\(2\)就是一个反例

上面这个图如果缩点双会缩成\(3\)个,但是缩边双会将整个图缩成\(1\)个点。

假如我们询问的是\((1,4)\)之间的简单路径,而图中权值最小的点为\(7\)号点,那么如果缩成了边双联通分量,你的答案会是\(7\)号点的权值,意即认为可以走到\(7\)号点,但实际上如果到\(7\)号点,意味着\(5\)号点需要经过\(2\)次,不符合简单路径的要求

所以如果将题意改成“一条边只能经过一次”就是缩边双了

那么我们直接维护圆方树,对于每一个方点使用\(multiset\)维护与它相连的所有圆点的权值,在圆方树上树链剖分计算答案。

当然这样子还是不够的。考虑一种情况:一个圆点连接了一堆方点,然后在这一个圆点上不断进行修改操作,这样每一次修改都会波及一大堆方点的修改,复杂度直接爆炸。

优化:对于所有方点,不去维护它在圆方树上的父亲,那么对于每一次修改,只会波及它在圆方树上的方点父亲。而如果在某一次询问中两点之间的\(LCA\)为方点,还需要额外考虑这个点的父亲的贡献。

#include<bits/stdc++.h>
#define lch (x << 1)
#define rch (x << 1 | 1)
#define mid ((l + r) >> 1)
#define INF 0x7fffffff
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == ‘-‘)
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 2e5 + 7;
struct Edge{
    int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , val[MAXN] , N , M , Q , cntEd , cnt;
int topS , ts , st[MAXN] , dfn[MAXN] , low[MAXN];
int ind[MAXN] , rk[MAXN] , fa[MAXN] , dep[MAXN] , son[MAXN] , sz[MAXN] , top[MAXN] , TS;
int Tree[MAXN << 2];
bool vis[MAXN];
vector < int > ch[MAXN];
multiset < int > s[MAXN];

inline void addEd(int a , int b){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    head[a] = cntEd;
}

inline void pop(int t , int bot){
    ch[t].push_back(++cnt);
    do{
        ch[cnt].push_back(st[topS]);
        s[cnt].insert(val[st[topS]]);
    }while(st[topS--] != bot);
}

void tarjan(int x , int p){
    st[++topS] = x;
    dfn[x] = low[x] = ++ts;
    vis[x] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(Ed[i].end != p)
            if(!vis[Ed[i].end]){
                tarjan(Ed[i].end , x);
                low[x] = min(low[x] , low[Ed[i].end]);
                if(low[Ed[i].end] >= dfn[x])
                    pop(x , Ed[i].end);
            }
            else
                low[x] = min(low[x] , dfn[Ed[i].end]);
}

void dfs1(int x , int p){
    fa[x] = p;
    dep[x] = dep[p] + 1;
    sz[x] = 1;
    for(int i = 0 ; i < ch[x].size() ; ++i){
        dfs1(ch[x][i] , x);
        sz[x] += sz[ch[x][i]];
        if(sz[son[x]] < sz[ch[x][i]])
            son[x] = ch[x][i];
    }
}

void dfs2(int x , int t){
    ind[x] = ++TS;
    rk[TS] = x;
    top[x] = t;
    if(!son[x])
        return;
    dfs2(son[x] , t);
    for(int i = 0 ; i < ch[x].size() ; ++i)
        if(ch[x][i] != son[x])
            dfs2(ch[x][i] , ch[x][i]);
}

inline void pushup(int x){
    Tree[x] = min(Tree[lch] , Tree[rch]);
}

void init(int x , int l , int r){
    if(l == r)
        Tree[x] = rk[l] <= N ? val[rk[l]] : *s[rk[l]].begin();
    else{
        init(lch , l , mid);
        init(rch , mid + 1 , r);
        pushup(x);
    }
}

void modify(int x , int l , int r , int tar , int num){
    if(l == r)
        Tree[x] = num;
    else{
        if(mid >= tar)
            modify(lch , l , mid , tar , num);
        else
            modify(rch , mid + 1 , r , tar , num);
        pushup(x);
    }
}

int query(int x , int l , int r , int L , int R){
    if(l >= L && r <= R)
        return Tree[x];
    int minN = INF;
    if(mid >= L)
        minN = min(minN , query(lch , l , mid , L , R));
    if(mid < R)
        minN = min(minN , query(rch , mid + 1 , r , L , R));
    return minN;
}

int work(int x , int y){
    int tx = top[x] , ty = top[y] , minN = INF;
    while(tx != ty){
        if(dep[tx] < dep[ty]){
            swap(x , y);
            swap(tx , ty);
        }
        minN = min(minN , query(1 , 1 , cnt , ind[tx] , ind[x]));
        x = fa[tx];
        tx = top[x];
    }
    if(dep[x] > dep[y])
        swap(x , y);
    minN = min(minN , query(1 , 1 , cnt , ind[x] , ind[y]));
    if(x > N)
        minN = min(minN , val[fa[x]]);
    return minN;
}

inline char getc(){
    char c = getchar();
    while(!isupper(c))
        c = getchar();
    return c;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    cnt = N = read();
    M = read();
    Q = read();
    for(int i = 1 ; i <= N ; ++i)
        val[i] = read();
    for(int i = 1 ; i <= M ; ++i){
        int a = read() , b = read();
        addEd(a , b);
        addEd(b , a);
    }
    tarjan(1 , 0);
    dfs1(1 , 0);
    dfs2(1 , 1);
    init(1 , 1 , cnt);
    while(Q--)
        if(getc() == ‘A‘)
            printf("%d\n" , work(read() , read()));
        else{
            int a = read() , b = read();
            if(a != 1){
                s[fa[a]].erase(s[fa[a]].find(val[a]));
                s[fa[a]].insert(b);
                modify(1 , 1 , cnt , ind[fa[a]] , *s[fa[a]].begin());
            }
            val[a] = b;
            modify(1 , 1 , cnt , ind[a] , b);
        }
    return 0;
}

原文地址:https://www.cnblogs.com/Itst/p/10289571.html

时间: 2024-10-01 04:22:50

CF487E Tourists 圆方树、树链剖分的相关文章

Tourists——圆方树

CF487E Tourists 一般图,带修求所有简单路径代价. 简单路径,不能经过同一个点两次,那么每个V-DCC出去就不能再回来了. 所以可以圆方树,然后方点维护一下V-DCC内的最小值. 那么,从任意一个割点进入这个DCC,必然可以绕一圈再从另一个割点出去. 所以,路径上的最小值,就是圆方树路径上的最小值.方点的最小值就是在这个DCC中走一走得到的. 树链剖分+线段树维护路径 用堆维护方点四周的圆点的最小值.然后更新. 一个问题是: 更新一个割点圆点,会影响到四周所有的方点.暴力更新,菊花

CF487E Tourists 【圆方树 + 树剖 + 堆】

题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用了一对一的特性优化了复杂度 如此询问时如果\(lca\)为方点,再询问一下\(lca\)的父亲即可 复杂度\(O(qlog^2n)\) #include<algorithm> #include<iostream> #include<cstring> #include<

【GDOI2020模拟01.16】树上的鼠 (博弈+长链剖分优化dp)

https://gmoj.net/senior/#contest/show/2989/2 思考什么时候先手会赢. 一开始双方都不会希望走到直径的端点上,因为那样对方就可以走直径而使自己输掉. 删掉直径的端点,考虑剩下的树的子问题. 如果又走到端点去了,对面就走到另外一个端点,那我就走到下一层的直径端点去了. 所以大家一直都不想走到直径端点. 一直删,如果最后只剩1一个点,说明先手必败,否则先手必胜. 如果是一条链,就是链的两边的长度不等先手就必胜. 如果是一棵树,考虑随便找一条直径,每次删去它的

CF487E Tourists (圆方树,LCT)

圆方树模板题. 建出圆方树. 对于每个方点,只维护方点儿子的最小值,不维护方点父亲的值,这样的话每次修改只会改一个方点. 我们需要支持单点修改,链查询,求 lca. LCT 可以非常方便地维护这些东西,然后如果 lca 是方点的话特判一下方点父亲的点值即可. code: #include <cstdio> #include <algorithm> #include <set> #include <vector> #include <cstring>

[SDOI2018]战略游戏 圆方树,树链剖分

[SDOI2018]战略游戏 这题是道路相遇(题解)的升级版,询问的两个点变成了\(S\)个点. LG传送门 还是先建出圆方树,考虑对于询问的\(S\)个点,答案就是圆方树上能包含这些点的最小连通块中的圆点个数减去\(S\).问题变成了怎样求这样的连通块中的圆点个数,直接给结论吧:先搞出树的dfs序,把询问的点按dfs序从小到大排一遍序,每次把答案加上第\(i\)和第\(i + 1\)个点之间的圆点个数,但是不算lca,再加上第\(1\)个和第\(S\)个点之间的圆点个数,然后除以二就得到了这个

圆方树总结

圆方树:一种将由图转化而成的树,从而大大了增加题目的可解性,且大多广泛用于仙人掌图中. 针对仙人掌图上的圆方树:仙人掌是指一条边至多只被一个环包含的无向图. 树上的点:圆方树上分为两类点,一类是圆点,一类是方点.圆点即原图中所有的点,方点即为了去环而新添加进去的,满足一定性质的点. 构造思路:圆圆边直接加入,对于仙人掌中的任意一个环,每个环上的点在圆方树上对应的圆点向这个环对应的方点连边,方点为一个新建节点. 环的根:指定一个圆点为圆方树的根,把方点的父亲叫做这个方点对应的环的根. 圆方边边权:

树分治&amp;树链剖分相关题目讨论

预备知识 树分治,树链剖分   poj1741 ?一棵有n个节点的树,节点之间的边有长度.方方方想知道,有多少个点对距离不超过m 题解 点分治模板题.详见我早上写的http://www.cnblogs.com/chouti/p/5836926.html   OrzFang Ⅸ ?有一棵n个点,边长为1的树,他要在树上选择一个大小为m的点集,使得这m个点两两距离相等. 方方方想知道这么做的方案数对998244353取模后的结果. 题解 首先肯定有一个中心点,使得这个点到m个点距离相等 那么枚举这个

HDU5221 Occupation 树链剖分

题意: 给出一棵树,root=1,树有点权,有一个人叫做M 有3种操作: 1 u v 把u到v路径上的所有点的点权都给M 2 u 若u的点权在M手上,拿走 3 u 把u为根的子树的所有点权都给M 每一个操作过后,输出M拥有的点权 想法: 要维护路径,用树链剖分 要维护子树,用dfs序 但是这样貌似要写很多 然而后来知道 树链剖分是有dfs序的,也就是说,树链剖分后,对于一个点,其子树所有点的新编号刚好在该点新编号后面的一个连续的区间里面,这个区间的范围[chg[u],chg[u]+siz[u]-

HDU 5274 Dylans loves tree(LCA+dfs时间戳+成段更新 OR 树链剖分+单点更新)

Problem Description Dylans is given a tree with N nodes. All nodes have a value A[i].Nodes on tree is numbered by 1∼N. Then he is given Q questions like that: ①0 x y:change node x′s value to y ②1 x y:For all the value in the path from x to y,do they