【BZOJ-3653】谈笑风生 DFS序 + 可持久化线段树

3653: 谈笑风生

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 628  Solved: 245
[Submit][Status][Discuss]

Description

设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。

Input

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

Output

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

Sample Input

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

Sample Output

3
1
3

HINT

1<=P<=N
1<=K<=N
N<=300000
Q<=300000

Source

Solution

答案显然是$(size[x]-1)*min(deep[x],K)+\sum_{dis(x,y)<=K,y\epsilon x}^{y}(size[y]-1)$

考虑求后面那些东西,可以利用可持久化线段树来做

用deep作为下标,维护size。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<‘0‘ || ch>‘9‘) {if (ch==‘-‘) f=-1; ch=getchar();}
    while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}
#define MAXN 300010
#define LL long long
int N,Q;
struct EdgeNode{int next,to;}edge[MAXN<<1];
int head[MAXN],cnt=1;
void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;}
void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);}
int deep[MAXN],size[MAXN],pl[MAXN],pr[MAXN],pre[MAXN],dfn,maxd;
void DFS(int now,int last)
{
    pl[now]=++dfn; pre[dfn]=now; size[now]=1;
    maxd=max(maxd,deep[now]);
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=last)
            {
                deep[edge[i].to]=deep[now]+1;
                DFS(edge[i].to,now);
                size[now]+=size[edge[i].to];
            }
    pr[now]=dfn;
}
struct SegmentTreeNode{int ls,rs; LL sum;}tree[MAXN*20];
int root[MAXN],sz;
void Update(int &now,int last,int l,int r,int pos,int val)
{
    now=++sz;
    tree[now].sum=tree[last].sum+val;
    if (l==r) return;
    int mid=(l+r)>>1;
    tree[now].ls=tree[last].ls,tree[now].rs=tree[last].rs;
    if (pos<=mid) Update(tree[now].ls,tree[last].ls,l,mid,pos,val);
            else  Update(tree[now].rs,tree[last].rs,mid+1,r,pos,val);
}
LL Query(int now,int l,int r,int L,int R)
{
    if (!now) return 0LL;
    if (L==l && R==r) return tree[now].sum;
    int mid=(l+r)>>1;
    if (R<=mid) return Query(tree[now].ls,l,mid,L,R);
    else if (L>mid) return Query(tree[now].rs,mid+1,r,L,R);
    else return Query(tree[now].ls,l,mid,L,mid)+Query(tree[now].rs,mid+1,r,mid+1,R);
}
int main()
{
    N=read(),Q=read();
    for (int x,y,i=1; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
    DFS(1,0);
//    for (int i=1; i<=N; i++)
//        printf("%d [%d , %d] %d %d\n",pre[i],pl[pre[i]],pr[pre[i]],size[pre[i]],deep[pre[i]]);
    for (int i=1; i<=N; i++) Update(root[i],root[i-1],0,maxd,deep[pre[i]],size[pre[i]]-1);
    for (int i=1; i<=Q; i++)
        {
            int p=read(),K=read();
            LL ans=0LL;
            ans=(LL)(size[p]-1)*min(deep[p],K)+Query(root[pr[p]],0,maxd,deep[p]+1,deep[p]+K)-Query(root[pl[p]-1],0,maxd,deep[p]+1,deep[p]+K);
            printf("%lld\n",ans);
        }
    return 0;
}

被Char哥-拍死了

时间: 2024-10-09 00:04:09

【BZOJ-3653】谈笑风生 DFS序 + 可持久化线段树的相关文章

BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并

题目大意:给出一个森林,每个节点都有一个权值.有若干加边操作,问两点之间路径上的第k小权值是多少. 思路:这题和COT1比较像,但是多了连接操作.这样就只能暴力合并连个树.启发式合并会保证时间复杂度不至于太大.然后就是用可持久化线段树维护一个树的信息,按照dfs序来建树,每个节点的可持久化链的参考版本就是它父亲的版本.之后利用权值线段树可区间加减的特性,用f[x] + f[y] - f[lca] - f[father[lca]]来计算权值. CODE: #include <cstdio> #i

hdu 6191 Query on A Tree(dfs序+可持久化字典树)

题目链接:hdu 6191 Query on A Tree 题意: 给你一棵树,每个节点有一个值,现在有q个询问,每个询问 询问一个u x,问以u为根的子树中,找一个节点,使得这个节点的值与x异或的值最大,输出那个最大的值. 题解: dfs序和一棵可持久化字典树就搞定了. 1 #include<bits/stdc++.h> 2 #define mst(a,b) memset(a,b,sizeof(a)) 3 #define F(i,a,b) for(int i=a;i<=b;++i) 4

【树链剖分】【dfs序】【线段树】bzoj2836 魔法树

这道题告诉我们:树链剖分的重标号就是dfs序. #include<cstdio> #include<algorithm> using namespace std; #define N 100001 #define lson rt<<1,l,m #define rson rt<<1|1,m+1,r typedef long long ll; ll delta[N<<2],sumv[N<<2]; int n,m; int en,v[N],

BZOJ 2653 middle 二分答案+可持久化线段树

题目大意:给定一个长度为n的序列,求当子序列s的左端点在[a,b],右端点在[c,d]时的最大中位数 其中当序列长度为偶数时中位数定义为中间两个数中较大的那个 很难想的一道题 具体题解见 http://blog.csdn.net/acm_cxlove/article/details/8566093 说的很详细 区间处理那里 [b,c]是必选的 [a,b)和(c,d]每段取最大加和 否则re恒>=0 #include<cstdio> #include<cstring> #inc

BZOJ 3932 CQOI2015 任务查询系统 可持久化线段树

题目大意见http://pan.baidu.com/s/1o6zajc2 主席树裸上就好了... #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 100100 using namespace std; struct Segtree{ Segtree *ls,*rs; int size; long long sum; void* op

BZOJ 3551 ONTAK2010 Peaks加强版 Kruskal重构树+可持久化线段树

题目大意:同3545 强制在线 3545题解传送门:http://blog.csdn.net/popoqqq/article/details/40660953 强制在线没法排序 启发式合并也就用不了了 Kruskal重构树是个挺好玩的东西 可以拿来处理一些最小生成树的边权最值问题 这里我们Kruskal连边时并不直接连边 而是新建一个节点ext 将两个点所在子树都连到ext的儿子上 比如说样例的树就建成了这样 图中红色的是原图的边权,黑色的是原图上的点 这样生成的树有一些十分优美的性质: 1.二

Tsinsen A1505. 树(张闻涛) 倍增LCA,可持久化线段树,DFS序

题目:http://www.tsinsen.com/A1505 A1505. 树(张闻涛) 时间限制:1.0s   内存限制:512.0MB 总提交次数:196   AC次数:65   平均分:58.62 将本题分享到: 查看未格式化的试题   提交   试题讨论 试题来源 2013中国国家集训队第二次作业 问题描述 给定一棵N个节点的树,每个点有一个权值,有M个询问(a,b,c)若a 为1,回答b到c路径上的最小权值,若a为2,回答b到c路径上的最大权值,若a为3,回答b到c路径上的所有权值的

[BZOJ 3218] A + B Problem 【可持久化线段树 + 网络流】

题目连接:BZOJ - 3218 题目分析 题目要求将 n 个点染成黑色或白色,那么我们可以转化为一个最小割模型. 我们规定一个点 i 最后属于 S 集表示染成黑色,属于 T 集表示染成白色,那么对于每个点 i 就要连边 (S, i, B[i]) 和 (i, T, W[i]). 这样,如果一个点属于 S 集,就要割掉与 T 相连的边,就相当于失去了染成白色的收益. 我们再来考虑 “奇怪的点”,一个点 i 变成奇怪的点的条件是:i 是黑色且存在一个白色点 j 满足 j < i && L

[BZOJ 3207] 花神的嘲讽计划Ⅰ【Hash + 可持久化线段树】

题目链接:BZOJ - 3207 题目分析 先使用Hash,把每个长度为 k 的序列转为一个整数,然后题目就转化为了询问某个区间内有没有整数 x . 这一步可以使用可持久化线段树来做,虽然感觉可以有更简单的做法,但是我没有什么想法... 代码 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #inclu