[P3899 [湖南集训] 更为厉害]

P3899 [湖南集训] 更为厉害

题目描述

\(\text T\) 为一棵有根树,我们做如下的定义:

  • \(a\)\(b\)\(\text T\) 中的两个不同节点。如果 \(a\)\(b\) 的祖先,那么称“\(a\)\(b\) 更为厉害”。
  • \(a\)\(b\)\(\text T\) 中的两个不同节点。如果 \(a\)\(b\) 在树上的距离不超过某个给定常数 \(x\),那么称“ \(a\)\(b\) 彼此彼此”。

给定一棵 \(n\) 个节点的有根树 \(\text T\),节点的编号为 \(1\)\(n\),根节点为 \(1\) 号节点。
你需要回答 \(q\) 个询问,询问给定两个整数 \(p\)\(k\),问有多少个有序三元组 \((a,b,c)\) 满足:

  1. \(a,b,c\)\(\text T\) 中三个不同的点,且 \(a\)\(p\) 号节点;
  2. \(a\)\(b\) 都比 \(c\) 更为厉害;
  3. \(a\)\(b\) 彼此彼此。这里彼此彼此中的常数为给定的 \(k\)

数据范围:

\(n\le 3*10^5\)

Solution:

话说之前暑假的时候貌似就见过这题,当时它貌似叫“谈笑风生”。

事先声明:本文中的 \(dep\) 是在根节点的深度为1的基础上定义的,同时,我将使用 \(S(x)\) 表示点 x 的子树
首先我们来思考一下那些点会产生贡献:

拿样例来看,对于询问 (2,2):合法的三元组有 \((2,1,4)\)\((2,1,5)\)\((2,4,5)\)

我们先重新描述一下贡献:我们发现三元组中的 \(a\) , \(b\) 并无区别,所以我们假设 (a,b,c) 的深度是单调递增的

不难看出其实我们可以把贡献拆为两部分:以点 \(p\) 做为三元组中的 \(b\) 的贡献和以点 \(p\) 作为 \(a\) 的贡献

我们先求以点 \(p\) 做为三元组中的 \(b\) 的贡献:
在这种情况下,(fa[p]->1) 路径上的所有点都可以作为 \(a\) ,\(p\) 的子树内的所有节点都可以作为 \(c\) ,因此,该部分的贡献为:
\(min(dep[p]-1,k)*(siz[p]-1)\)

然后对于以点 \(p\) 作为 \(a\) 的贡献:
假设我们已经在点 p 的子树内钦定了一个点 \(q\) 作为 \(b\) ,那么 \(c\) 就应该从 \(q\) 的子树内进行选取,也就是说,这部分的贡献是:

$$\sum_{q\in{S(p)}|dis(p,q)\in[1,k]} siz[q]-1$$

我们不难想到用线段树合并来维护这个式子,我们以\(dep[x]\) 为下标 \(siz[x]-1\) 为点权建立一颗权值线段树,然后向上合并,那么对于每个点 \(p\) 上能取到的贡献就是 \(dep\in[dep_p,dep_p+k]\) 这段区间的总和

然后说一些细节:

由于是线段树合并,所以如果想要在线做这道题的话在 merge 的时候一定要新建一个节点作为合并的结果,否则就会使得一个点上挂有同层其他节点的贡献,这样显然是错误的

但是我们发现本题可以离线,所以我们将每个询问挂到点上,我们在构建完一个点的线段树后直接查询,在其他节点乱入之前完成答案统计。

Code:

#include<bits/stdc++.h>
#define ll long long
const int N=6e5+5;
using namespace std;
struct Segment_Tree{
    int rt[N],cnt;
    struct Tree{
        int ls,rs;
        ll cnt;
    }t[N*40];
    void pushup(int x){t[x].cnt=t[t[x].ls].cnt+t[t[x].rs].cnt;}
    void insert(int &x,int l,int r,int pos,ll val)
    {
        t[x= (x? x : ++cnt)].cnt+=val;
        if(l==r)return;
        int mid=l+r>>1;
        if(pos<=mid)insert(t[x].ls,l,mid,pos,val);
        if(mid<pos) insert(t[x].rs,mid+1,r,pos,val);
    }
    int merge(int x,int y,int l,int r)
    {
        if(!x||!y)return x|y;
        if(l==r){t[x].cnt+=t[y].cnt;return x;}
        int mid=l+r>>1;
        t[x].ls=merge(t[x].ls,t[y].ls,l,mid);
        t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r);
        pushup(x);
        return x;
    }
    ll query(int x,int l,int r,int L,int R)
    {
        if(!x)return 0;
        if(L<=l&&r<=R){return t[x].cnt;}
        int mid=l+r>>1;ll res=0;
        if(L<=mid)res+=query(t[x].ls,l,mid,L,R);
        if(mid<R) res+=query(t[x].rs,mid+1,r,L,R);
        return res;
    }
}T;
int n,m;
vector<int>E[N];
int dep[N],st[N],ed[N],siz[N];
vector<tuple<int,int> > Q[N];
ll ans[N];
struct Graph{
    void dfs(int x,int fa)
    {
        dep[x]=dep[fa]+1;siz[x]=1;
        for(auto y : E[x])
        {
            if(y==fa)continue;
            dfs(y,x);
            siz[x]+=siz[y];
        }
        T.insert(T.rt[x],1,n,dep[x],siz[x]-1);
        for(auto y : E[x]){if(y==fa)continue;T.rt[x]=T.merge(T.rt[x],T.rt[y],1,n);}
        for(auto [k,id] : Q[x])
        {
            ans[id]=T.query(T.rt[x],1,n,dep[x]+1,dep[x]+k)+(1ll*siz[x]-1)*1ll*min(1ll*dep[x]-1,1ll*k);
        }
    }

}G;
void work()
{
    cin>>n>>m;
    for(int i=1,x,y;i<n;i++){cin>>x>>y;E[x].push_back(y);E[y].push_back(x);}
    for(int i=1,x,k;i<=m;i++)
    {
        cin>>x>>k;
        Q[x].emplace_back(k,i);
    }
    G.dfs(1,0);
    for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";
}
int main()
{
    //freopen("P3899.in","r",stdin);freopen("P3899.out","w",stdout);
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    work();
    return 0;
}
posted @ 2025-01-11 16:46  liuboom  阅读(13)  评论(0)    收藏  举报