题目地址:P5305 [GXOI/GZOI2019]旧词

这里是官方题解

\[\sum_{i \leq x}^{}\ depth(lca(i,y))^k\]

\(k = 1\)

求的是 \(\sum_{i \leq x}^{}\ depth(lca(i,y))\) ,一堆点然后每个点和 \(y\) 求 \(lca\) 然后深度求和。

总体思路是把 \(lca\) 的值摊派到这个点到根的路径上(这个东西也叫树上差分?),再离线解决所有询问。

维护一个点权数组 \(sum\) ,初始为 \(0\) ,然后将 \(y\) 到根的每个点的点权设为 \(1\) ,然后对于每个点 \(i \leq x\) ,求从 \(i\) 到根的权值和为上面要求的答案,但这样就 \(O(n)\) 了。

(可以反向考虑),维护一个点权数组 \(sum\) ,初始为 \(0\) ,对于小于等于 \(x\) 的点 \(i\) ,将 \(i\) 到根的路径上所有点的点权 ++ 。然后求从 \(y\) 到根的权值和也是上面要求的答案。这种方法求可以按 \(x\) 排序,然后离线, \(x\) 相等的询问一块问。

可以树链剖分 + 线段树解决。 \(O(nlog^2n)\) 。或者 LCT 也行。

\(k > 1\)

\(k = 2\) 的话,按照上述思路想,把 \(lca^2\) 的值摊到到根的路径上的话就不是之前的 \(1,1,1,...\) ,变成了 \(1,3,5,...\) 直接看的话问题变成了线段树区间加等差数列,好像改一下线段树实现也能做(所以给了点部分分)。

但是 \(k > 2\) 的时候就比较麻烦了。

基于把 \(lca^k\) 摊到从这个点到根的路径上这个思路,实际上对于深度是 \(x\) 的点来说,这个点每次点权增加的值固定是 \(x ^ k - (x - 1) ^ k\) 。

所以实际上,线段树打标记只用记录每个点被算了多少次 \(cnt\) 即可。然后实际上的权值和是 \(sum_i = cnt_i * (dep_i^k -(dep_i - 1)^k)\) ,每次操作只有 \(cnt\) 区间加 \(1\) ,于是预处理线段树上每个区间的 \(\sum (dep_i^k -(dep_i - 1)^k)\) 后就可以直接拿线段树维护 \(sum\) 数组。

于是还是之前的树链剖分 + 线段树解决。 \(O(nlog^2n)\) 。或者 LCT 也行。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>

// by zrt
// zhangruotian@foxmail.com

using namespace std;

typedef long long LL;

const int MAXN = 50000+5;
const LL mod = 998244353;

int n,Q,k;
int H[MAXN], X[MAXN], P[MAXN], tot;
int fa[MAXN], dep[MAXN], dfn[MAXN], invdfn[MAXN], siz[MAXN], son[MAXN], top[MAXN];

inline void add(int x,int y) {
    P[++tot]=y;
    X[tot]=H[x];
    H[x]=tot;
}

struct Query {
    int x,y,id;
} query[MAXN];

int ans[MAXN];

bool cmp_by_x(Query a, Query b) {
    return a.x<b.x;
}

void dfs1(int x) {
    dep[x] = dep[fa[x]] + 1;
    siz[x] = 1;
    for(int i=H[x]; i; i=X[i]) {
        dfs1(P[i]);
        siz[x] += siz[P[i]];
        if(siz[P[i]]>siz[son[x]]) {
            son[x] = P[i];
        }
    }
}

int tim;

void dfs2(int x) {
    dfn[x] = ++tim;
    if(!top[x]) top[x]=x;
    if(son[x]) top[son[x]] = top[x], dfs2(son[x]);
    for(int i=H[x]; i; i=X[i]) {
        if(P[i] == son[x]) continue;
        dfs2(P[i]);
    }
}

LL pow(LL a,LL b,LL p) {
    LL ret = 1%p;
    while(b) {
        if(b&1) ret = ret*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ret;
}

const int MAXN4 = MAXN*4;
struct SEGMENT_TREE {
    LL sum[MAXN4], cnt[MAXN4], pre[MAXN4];
    void updsum(int o) {
        sum[o] = (sum[o<<1] + sum[o<<1|1])%mod;
    }
    void bd(int o,int l,int r) {
        if(l==r) {
            pre[o] = (pow(dep[invdfn[l]],k,mod) - pow(dep[invdfn[l]]-1,k,mod) + mod)%mod;
            cnt[o] = sum[o] = 0;
        } else {
            int mid=(l+r)/2;
            bd(o<<1,l,mid);
            bd(o<<1|1,mid+1,r);
            pre[o] = (pre[o<<1]+pre[o<<1|1])%mod;
        }
    }
    void build() {
        bd(1,1,n);
    }
    void down(int o) {
        if(cnt[o]>0) {
            sum[o<<1] = (sum[o<<1]+(pre[o<<1]*cnt[o])%mod)%mod;
            cnt[o<<1] += cnt[o];
            sum[o<<1|1] = (sum[o<<1|1]+(pre[o<<1|1]*cnt[o])%mod)%mod;
            cnt[o<<1|1] += cnt[o];
            cnt[o]=0;
        }
    }
    void add(int o,int l,int r,int L,int R) {
        if(l == L && r == R) {
            sum[o] = (sum[o]+pre[o]) % mod;
            cnt[o] ++;
        } else {
            int mid=(l+r)/2;
            down(o);
            if(R<=mid) add(o<<1,l,mid,L,R);
            else if(L>mid) add(o<<1|1,mid+1,r,L,R);
            else add(o<<1,l,mid,L,mid), add(o<<1|1,mid+1,r,mid+1,R);
            updsum(o);
        }
    }
    void add(int l,int r) {
        assert(l<=r);
        add(1,1,n,l,r);
    }
    LL ask(int o,int l,int r,int L,int R) {
        if(l==L && r == R) {
            return sum[o];
        } else {
            int mid=(l+r)/2;
            down(o);
            if(R<=mid) return ask(o<<1,l,mid,L,R);
            else if(L>mid) return ask(o<<1|1,mid+1,r,L,R);
            else return (ask(o<<1,l,mid,L,mid) + ask(o<<1|1,mid+1,r,mid+1,R))%mod;
        }
    }
    LL ask(int l,int r) {
        assert(l<=r);
        return ask(1,1,n,l,r);
    }
} tree;

void prepare() {
    dfs1(1);
    dfs2(1);
    assert(tim == n);
    for(int i=1; i<=n; i++) invdfn[dfn[i]] = i;
    tree.build();
}

void add_point(int x) {
    while(x) {
        tree.add(dfn[top[x]], dfn[x]);
        x=fa[top[x]];
    }
}

LL ask(int x) {
    LL ret = 0;
    while(x) {
        ret = (ret + tree.ask(dfn[top[x]], dfn[x]))%mod;
        x=fa[top[x]];
    }
    return ret;
}

int main() {
    scanf("%d%d%d",&n,&Q,&k);
    assert(n+5 <= MAXN );
    assert(Q+5 <= MAXN );

    for(int i=2,x; i<=n; i++) {
        scanf("%d",&x);
        add(x,i);
        fa[i]=x;
    }
    for(int i=1; i<=Q; i++) {
        scanf("%d%d",&query[i].x,&query[i].y);
        query[i].id = i;
    }

    prepare();

    sort(query+1,query+Q+1,cmp_by_x);
    int p = 1;
    for(int i=1; i<=n; i++) {
        add_point(i);
        while(p <= Q && query[p].x == i) {
            ans[query[p].id] = ask(query[p].y);
            p++;
        }
    }
    assert(p==Q+1);

    for(int i=1; i<=Q; i++) {
        printf("%d\n",ans[i]);
    }

    return 0;
}

P5305 [GXOI/GZOI2019]旧词的更多相关文章

  1. luogu P5305 [GXOI/GZOI2019]旧词

    传送门 先考虑\(k=1\),一个点的深度就是到根节点的路径上的点的个数,所以\(lca(x,y)\)的深度就是\(x\)和\(y\)到根路径的交集路径上的点的个数,那么对于一个询问,我们可以对每个点 ...

  2. [LOJ3088][GXOI/GZOI2019]旧词——树链剖分+线段树

    题目链接: [GXOI/GZOI2019]旧词 对于$k=1$的情况,可以参见[LNOI2014]LCA,将询问离线然后从$1$号点开始对这个点到根的路径链修改,每次询问就是对询问点到根路径链查询即可 ...

  3. 【BZOJ5507】[GXOI/GZOI2019]旧词(树链剖分,线段树)

    [BZOJ5507][GXOI/GZOI2019]旧词(树链剖分,线段树) 题面 BZOJ 洛谷 题解 如果\(k=1\)就是链并裸题了... 其实\(k>1\)发现还是可以用类似链并的思想,这 ...

  4. BZOJ5507 GXOI/GZOI2019旧词 (树链剖分+线段树)

    https://www.cnblogs.com/Gloid/p/9412357.html差分一下是一样的问题.感觉几年没写过树剖了. #include<iostream> #include ...

  5. [GXOI/GZOI2019]旧词(树上差分+树剖)

    前置芝士:[LNOI2014]LCA 要是这题放HNOI就好了 原题:\(\sum_{l≤i≤r}dep[LCA(i,z)]\) 这题:\(\sum_{i≤r}dep[LCA(i,z)]^k\) 对于 ...

  6. [GXOI/GZOI2019]旧词

    很像LNOI 2014 LCA那道题. 同样的套路,离线以后直接扫描线. k=1的话就是原题. 考虑一般情况. 原本的做法是对x到根的这条链做一下区间+1操作,目的是为了是的在深度为i的位置得到的贡献 ...

  7. [bzoj5507] [洛谷P5305] [gzoi2019]旧词

    Descriptioin 浮生有梦三千场 穷尽千里诗酒荒 徒把理想倾倒 不如早还乡 温一壶风尘的酒 独饮往事迢迢 举杯轻思量 泪如潮青丝留他方 --乌糟兽/愚青<旧词> 你已经解决了五个问 ...

  8. P5305-[GXOI/GZOI2019]旧词【树链剖分,线段树】

    正题 题目链接:https://www.luogu.com.cn/problem/P5305 题目大意 给一棵有根树和\(k\),\(Q\)次询问给出\(x,y\)求 \[\sum_{i=1}^{x} ...

  9. BZOJ 5507: [gzoi2019]旧词 LCT

    和之前那个 [LNOI]LCA 几乎是同一道题,就是用动态树来维护查分就行. code: #include <bits/stdc++.h> using namespace std; #de ...

随机推荐

  1. vedio-js的视频插件用法

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. Codeforces Round #522 (Div. 2, based on Technocup 2019 Elimination Round 3) D. Barcelonian Distance 几何代数(简单)

    题意:给出一条直线 ax +by+c=0  给出两个整点 (x1,y1) (x2,y2) 只有在x,y坐标至少有一个整点的时 以及   给出的直线才有路径(也就是格子坐标图的线上) 问 两个整点所需要 ...

  3. MYSQL OR与AND同时出现的用法

    表中数据 MySQL中or和and的用法 SELECT * from student WHERE id = 1 or id = 2 AND age = 20; -- 查询结果是id = 2且age = ...

  4. Verilog语言实现并行(循环冗余码)CRC校验

    1 前言 (1)    什么是CRC校验? CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能, ...

  5. beego框架开发投票网站(1) beego基础之运行逻辑

    本文档需结合beego官方文档食用 博主也仅仅是边学边记录,不保证内容的正确性,请当做通俗读物来看待 首先 beego是一个基于go语言的框架 其次 beego是一个mvc框架 框架可以理解为对底层又 ...

  6. JavaScript - proxy

    Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等). 来看看常用的方法 handler.get() let o = { name: 'liwenchi', age: 1 ...

  7. PWN! 第一次测试答案及讲解

    题目链接:https://vjudge.net/contest/279567#overview 题目密码:190118 1.A+B:(考察点:EOF输入.加法运算) Topic: Calculate ...

  8. java的集合:List、Set和Map

    虚线是接口,实线是实现类: 集合能够解决的问题:集合可以丽杰为是一种更高级的数组,可以保存多条数据 本质:java官方开发人员基于java的一些基础内容(数组等等)创建了一些接口和类,然后使用这些接口 ...

  9. Django 中的static文件的设置

    STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ('article',os.path.jo ...

  10. P4180 严格次小生成树[BJWC2010] Kruskal,倍增

    题目链接\(Click\) \(Here\). 题意就是要求一个图的严格次小生成树.以前被题面吓到了没敢做,写了一下发现并不难. 既然要考虑次小我们就先考虑最小.可以感性理解到一定有一种次小生成树,可 ...