【NOI模拟】谈笑风生(主席树)

题目描述

设 T 为一棵有根树,我们做如下的定义:

    • 设 a 和 b 为 T 中的两个不同节点。如果 a 是 b 的祖先,那么称 “ a 比 b 不知道高明到哪里去了 ” 。
    • 设 a 和 b 为 T 中的两个不同节点。如果 a 与 b 在树上的距离不超过某个给定常数 x ,那么称 “ a 与 b 谈笑风生 ” 。

给定一棵 n 个节点的有根树 T ,节点的编号为 1~n ,根节点为 1 号节点。你需要回答 q 个询问,询问给定两个整数 p 和 k ,问有多少个有序三元组 (a,b,c) 满足:
1. a、b 和 c 为 T 中三个不同的点,且 a 为 p 号节点;
2. a 和 b 都比 c 不知道高明到哪里去了; 
3. a 和 b 谈笑风生。这里谈笑风生中的常数为给定的 k 。

输入格式

输入的第一行含有两个正整数n和q,分别代表有根树的点数与询问的个数。
接下来 n-1 行,每行描述一条树上的边。每行含有两个整数 u 和 v ,代表在节点 u 和 v 之间有一条边。
接下来 q 行,每行描述一个操作。第 i 行含有两个整数,分别表示第 i 个询问的 p 和 k 。

输出格式

输出 q 行,每行对应一个询问,代表询问的答案。

样例输入

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

样例输出



3

题目分析

CODE:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N = 300005;
int n, q;
#define bug(x) cout<<#x<<":"<<x<<endl

inline int Re(){
    int i = 0, f = 1; char ch = getchar();
    for(; (ch < ‘0‘ || ch > ‘9‘) && ch != ‘-‘; ch = getchar());
    if(ch == ‘-‘) f = -1, ch = getchar();
    for(; ch >= ‘0‘ && ch <= ‘9‘; ch = getchar())
        i = (i << 3) + (i << 1) + (ch - ‘0‘);
    return i * f;
}

struct node{
    int lc, rc;
    ll sum;
}tr[N << 6];
int pool, root[N], sze[N], dep[N], in[N], out[N], id;

int ecnt, adj[N], go[N << 1], nxt[N << 1], maxx, pos[N];

inline void Insert(const int &x, int &y, const int &l, const int &r, const int &pos, const int &v){
    tr[y = ++pool] = tr[x];
    tr[y].sum += v;
    if(l == r) return;
    int mid = l + r >> 1;
    if(pos <= mid) Insert(tr[x].lc, tr[y].lc, l, mid, pos, v);
    else Insert(tr[x].rc, tr[y].rc, mid + 1, r, pos, v);
}

inline ll query(int pos, const int &nl, const int &nr, const int &l, const int &r){
    if(l <= nl && nr <= r) return tr[pos].sum;
    int mid = nl + nr >> 1;
    ll ret = 0;
    if(l <= mid) ret += query(tr[pos].lc, nl, mid, l, r);
    if(r > mid ) ret += query(tr[pos].rc, mid + 1, nr, l, r);
    return ret;
}

inline void addEdge(const int &u, const int &v){
    nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
    nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u;
}

inline void dfs(const int &u, const int &f){
    dep[u] = dep[f] + 1;
    maxx = max(maxx, dep[u]);
    sze[u] = 1;
    in[u] = ++id;
    pos[id] = u;
    int v;
    for(int e = adj[u]; e; e = nxt[e]){
        if((v = go[e]) == f) continue;
        dfs(v, u);
        sze[u] += sze[v];
    }
    out[u] = id;
}

inline void tree_init(){
    for(int i = 1; i <= n; i++){
        Insert(root[i - 1], root[i], 0, maxx, dep[pos[i]], sze[pos[i]] - 1);
    }
}

int main(){
    freopen("h.in", "r", stdin);
    n = Re(), q = Re();
    for(int i = 1; i < n; i++){
        int u = Re(), v = Re();
        addEdge(u, v);
    }
    maxx = -1; dep[0] = -1;
    dfs(1, 0);
    tree_init();
//    for(int i = 1; i <= n; i++) bug(i),bug(dep[pos[i]]);
    for(int i = 1; i <= q; i++){
        int p = Re();
        int k = Re();
        ll ans = 0;
        ans += (ll)(sze[p] - 1) * (ll)min(dep[p], k);
//        bug(ans);
        ans += query(root[out[p]], 0, maxx, min(dep[p] + 1, maxx), min(dep[p] + k, maxx));
//        bug(ans); bug(query(root[out[p]], 1, maxx, min(dep[p] + 1, maxx), min(dep[p] + k, maxx)));
        ans -= query(root[in[p] - 1], 0, maxx, min(dep[p] + 1, maxx), min(dep[p] + k, maxx));
        cout<<ans<<endl;
    }
    return 0;
}
时间: 2024-08-29 05:14:02

【NOI模拟】谈笑风生(主席树)的相关文章

[湖南集训] 谈笑风生 (主席树)

[湖南集训] 谈笑风生 题目描述 设 T 为一棵有根树,我们做如下的定义: ? 设 a 和 b 为 T 中的两个不同节点.如果 a 是 b 的祖先,那么称"a 比 b 不知道高明到哪里去了". ? 设 a 和 b 为 T 中的两个不同节点.如果 a 与 b 在树上的距离不超过某个给定常数 x,那么称"a 与 b 谈笑风生". 给定一棵 n 个节点的有根树 T,节点的编号为 1 - n,根节点为 1 号节点.你需要回答 q 个询问,询问给定两个整数 p 和 k,问有多

bzoj 3653 谈笑风生——主席树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3653 原来一直想怎么线段树合并.可是不会把角标挪一位. 查询的其实是子树内一段深度的点的 siz 和.因为是子树内,所以按 dfs 序建立主席树,角标是 dep ,值是 siz . 注意 long long .而且数组 *19 就会 RE ,*20就好了.不知为何. #include<iostream> #include<cstdio> #include<cstring

BZOJ 2006 NOI 2010 超级钢琴 堆+主席树

题目大意:给出一些音符,将它们组成和旋.和旋只能由[l,r]个音符组成.优美程度为所有音符的和.求k个和旋的又优美程度的最大和. 思路:先处理出来前缀和,以便O(1)去除一段的和.然后考虑对于一个音符来说,向左边扩展的音符是一段长度为r - l + 1的区间,取出的最大和是sum[i] - sum[p],sum[i]是一定的,要想让整段和最大,需要让sum[p]最小.之后就是区间k小值和堆得维护了,可以用时代的眼泪划分树,也可以用主席树. CODE: #include <vector> #in

[CSP-S模拟测试]:树(树上LIS+主席树+线段树)

题目传送门(内部题78) 输入格式 第一行输入两个整数$n,q$,表示节点数和询问数. 第二行输入$n$个整数$w_i$,表示第$i$个点的智商. 第三行至第$n+1$行每行输入两个数$x,y$,表示树上一条边. 第$n+2$行至第$n+q+1$行每行三个数$u,v,c$表示一次探究.(保证$v$是$u$的祖先) 输出格式 输出$q$行,每行两个数表示探究过程中$cwystc$需要努力学习的次数. 样例 见下发文件 数据范围与提示 对于$10\%$的数据:$n\leqslant 1,000$ 对

poj2104(主席树讲解)

今天心血来潮,突然想到有主席树这个神奇的玩意儿...一直都只是听说也没敢看.(蒟蒻蛋蛋的忧伤...) 然后到网上翻大神的各种解释...看了半天... 一拍脑袋...哇其实主席树 真的难...[咳咳我只是来搞笑的] 看了很多种解释最后一头雾水啊...就是没法脑补出(嗯没错经常脑补数据结构长啥样)主席树的样子.... 最后终于找到了一个大大大大大大神犇的ppt,看到了主席树的真面目,才真的能弄懂主席树的结构... -------------------------------------------

POJ_2104_Kth(主席树)

描述 http://poj.org/problem?id=2104 给出一个n个数的数列,m次询问,每次询问求区间[l,r]中第k小的数,无修改操作. 分析 静态的主席树裸题. 首先考虑把数据离散化,这样一共有n个数,分别为1,2,...,n-1,n(如果没有重复的话)(如果题目里面说有重复且重复数字排名相同,就去一下重就好了).用N=n的线段树来表示某一区间当前的情况,其中节点a[k]表示在这个区间内,属于[a[k].L,a[k].R]的数字共有多少.这样在这个区间上求第K小数的操作就类似于平

spoj cot: Count on a tree 主席树

10628. Count on a tree Problem code: COT You are given a tree with N nodes.The tree nodes are numbered from 1 to N.Each node has an integer weight. We will ask you to perform the following operation: u v k : ask for the kth minimum weight on the path

【POJ2104】K-th Number——主席树

早上刷NOIP的题刷到有点烦就想学点新东西,然后.....一个早上就这样过去了QAQ.虽然主席树不是NOIP考点,但是...或许我能活到省选呢?(美好的幻想) 题目链接 题目的大意就是给定一个长度为n的区间,给出m个询问,每次询问一个区间[l,r]中第k小的树. 主席树(一种可持久化线段树)的入门题. 推荐一发学习资料:戳这里 感觉人家讲得很仔细了我也没什么讲的必要了...... 总算是学了一种可持久化树了,好像也没想象中那么难?这道题的重点在query函数方面,建议自己在纸上模拟一下建树和查询

洛谷 3701「伪模板」主席树(最大流)

题目背景 byx和手气君都非常都非常喜欢种树.有一天,他们得到了两颗奇怪的树种,于是各自取了一颗回家种树,并约定几年后比一比谁种出来的树更加牛x. 题目描述 很快,这棵树就开花结果了.byx和手气君惊讶的发现,这是一棵主席树,树上长满了主席和主席的朋友们.这棵树上一共有五种人,主席(J),记者(HK),高人(W),女王(E)和膜法师(YYY).他们发现,他们的主席树上的人数相同,都为N.   研究发现,这五种人的输赢如上图所示(一样的人不能PK),箭头指向输的人.至于为什么,留给同学们自己思考.