更为厉害|P3899

https://www.luogu.com.cn/problem/P3899

题目描述

设 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 行,每行对应一个询问,代表询问的答案。

输入输出样例

输入 #1

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

输出 #1

3
1
3

说明/提示

样例中的树如下图所示:

对于第一个和第三个询问,合法的三元组有 (2,1,4)、 (2,1,5) 和 (2,4,5)。

对于第二个询问,合法的三元组只有 (4,2,5)。

所有测试点的数据规模如下:

对于全部测试数据的所有询问,1≤p,k≤n。


不急,先抽象题意

首先第一步,我们要看题目给的条件有没有什么结论,是不是有很强的限制

设 a 和 b 为 T 中的两个不同节点。如果 a 是 b 的祖先,那么称“a 比 b 更为厉害”。

设 a 和 b 为 T 中的两个不同节点。如果 a 与 b 在树上的距离不超过某个给定常数 x,那么称“ a 与 b 彼此彼此”。

a,b,c 为 T 中三个不同的点,且 a 为 p 号节点;

a 和 b 都比 c 更为厉害;

a 和 b 彼此彼此。这里彼此彼此中的常数为给定的 k。

copy过来方便看

a 和 b 都比 c 更为厉害;

这能说明什么?

c同时要是a和b的子孙。

那a,b一定也是祖孙关系

(这要解释吗。。。)

我也不会

如果一定是祖孙关系,那有个条件就变的简单了:

设 a 和 b 为 T 中的两个不同节点。如果 a 与 b 在树上的距离不超过某个给定常数 x,那么称“ a 与 b 彼此彼此”。

那就是不树上最短路跑LCA了,而是深度差。

于是题变成了:

给一个定点P,深度差不超过k的地方找个b,再在更深的那个节点的子树上取一个点c

取c的情况个数就是子树大小减1,dfs预处理很香;

当b是a的祖先时,a永远是更深的那个,c在它的子树上取,乘法原理即得此时情况数等于b的情况数乘上a子树大小减一;

要是a的深度大于k,那么b能选够k个,否则只有剩余深度那么多。

真正难的来了:当b是a的子孙时,在深度差为k的范围内把每种可能的b的子树和加起来

(上图!!!)

。。又。 隹。。;受。 。 。

这时候,我们如果能拿出一个柿子,那思路就可以继续往下了

$$ \sum_{dep_i \in (dep_p,dep_p+k],dfn_i \in (dfn_p,ma_p] }^{}(s_i-1) = \sum_{i=dep_p}^{dep_p+k}\sum_{j=dfn_p}^{ma_p}is(i,j) \times (s_i-1) $$

(变量看代码)

非常抽象,但是我们发现,我们找到了每个点的两个维度,要求符合两个维度限制的点之和。

于是我们把这些点撒到二维平面上,问题不就——

kaiyi的烦恼

退化了。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
//艹炸long long了
#define int long long 
/*
更为蒟蒻
kaiyi的疑惑 「?·问号·?」
c --> b --> a
*/
//快读读,读快快~
inline int read(){
    int s=0,f=1;
    char c=getchar();
    while(c<48 || c>57){if(c=='-')f=-f;c=getchar();}
    while(c>=48 && c<=57)s=s*10+c-48,c=getchar();
    return s*f;
}
const int MAXN=3e5+10;
//一棵状数组树是很好吃的
int c[MAXN];
int n,m;
#define lb ((i)&(-(i)))
int query(int x){
    if(x==0)return 0;
    int ret=0;
    for(int i=x;i>0;i-=lb)ret+=c[i];
    return ret;
}
void modify(int x,int v){
    if(x==0)return;
    for(int i=x;i<=n;i+=lb)c[i]+=v;
}
//存树就是存图 ——xky_OI_strong-2021.7 发行版
int head[MAXN];
int _cnt=0;
struct node{
    int to;
    int next;
}edge[MAXN*2];
void add(int u,int v){
    edge[_cnt].to=v;
    edge[_cnt].next=head[u];
    head[u]=_cnt++;
}
//现在我们要处理子树上有多少节点这个问题
//还有深度
//所以dfs!
int depth[MAXN];
int dfn[MAXN];
int s[MAXN];
int cnt=0;
int ma[MAXN];
void dfs(int x,int dep){
    s[x]=1;
    dfn[x]=++cnt;
    ma[x]=dfn[x];
    depth[x]=dep;
    for(int i=head[x];i!=-1;i=edge[i].next){
        int nxt=edge[i].to;
        if(dfn[nxt])continue;
        dfs(nxt,dep+1);
        ma[x]=max(ma[x],ma[nxt]);
        s[x]+=s[nxt];
    }
    return ;
}
//万事具备,只差二维偏序
/*
(dep,dfn)
dfn作为纵轴
y/dfn
^
|
|
|
|
*------------> x/dep
《 扫 描 线 》
*/
struct pt{int d;int v;};
vector<pt> point[MAXN*2];
struct nb{
    int l;
    int r;
    int o;
    bool is_plus;
};
vector <nb> problem[MAXN*2];
int ans[MAXN];
//lovely main func~
signed main(){
    memset(head,-1,sizeof(head));
    n=read();
    m=read();
    //init:
    int x,y;
    int k,p;
    for(int i=1;i<n;i++){
        x=read(),y=read();
        add(x,y),add(y,x);
    }
    dfs(1,1);//深度为1, 1 1 1~
    for(int i=1;i<=n;i++){
        point[depth[i]].push_back(pt{dfn[i],s[i]-1});
    }
    //询问
    for(int i=0;i<m;i++){
        p=read();
        k=read();
        /* 处理询问 */
        //二维限制 x: dep ∈ ( depth[x] , depth[x]+k ]
        //二维限制 y: dfn ∈ ( dfn[x] , ma[x] ]
        // --
        problem[depth[p]].push_back({dfn[p],ma[p],i,false});
        //++
        problem[depth[p]+k].push_back({dfn[p],ma[p],i,true});        
        //细节tm多死
        ans[i]=(s[p]-1)*min(depth[p]-1,k);
    }
    
    {//<<  扌彐 扌苗 纟戋  >>
        
        for(int i=1;i<=2*n;i++){
            for(auto j:point[i]){
                modify(j.d,j.v);
            }
            for(auto j:problem[i]){
                if(j.is_plus){
                    ans[j.o]+=query(j.r)-query(j.l);
                }
                else{
                    ans[j.o]-=query(j.r)-query(j.l);
                }
            }
        }
        
    }
    //输出喽~
    for(int i=0;i<m;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}