【BZOJ 3626】【LNOI 2014】LCA【树链剖分】

Description

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)

Input

第一行2个整数n q。
接下来n-1行,分别表示点1到点n-1的父节点编号。
接下来q行,每行3个整数l r z。

Output

输出q行,每行表示一个询问的答案。每个答案对201314取模输出

题解

此题实力树链剖分啊。
  首先,这道题在线没看到会做的人,但是离线也较难思考。
  然后,我们先简化一下,对于一组询问怎么处理?
  类似暴力的想法,我们把每要询问的点往上都打上标记,然后把z点往上找,第一次遇到的那个有标记的点就是它们的LCA。
  然后就我们可以发现,每个标记的深度等同于它上面有多少个和它一样的标记。 那么我们可以把每个点到根的路径都+1,然后询问z到根的和,就是答案了。
  很显然可以用树链剖分搞。
  那么多组询问:
    区间[l,r]的答案=区间[1,r]的答案-区间[1,l-1]的答案。
  那么我们把每个询问拆成两个,一个+,一个-。 然后排序,按顺序从1~n开始更新即可。

代码

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

#define N 50010
#define mod 201314
using namespace std;

struct node{int to,next;}e[N*2];
int head[N],m,tim,n;
int dep[N],size[N],top[N],fa[N],son[N],tid[N];
int ans[N];
struct data{int p,num,w,f;}a[N*2];
inline bool operator < (data a,data b) { return a.p < b.p; }

void init() {
    tim = m = 0;
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
}

void add_edge(int from,int to) {
    e[++m].next = head[from];
    head[from] = m;
    e[m].to = to;
}

void dfs1(int v,int d) {
    dep[v] = d; size[v] = 1;
    for(int i = head[v];i;i=e[i].next) {
        dfs1(e[i].to,d+1);
        size[v] += size[e[i].to];
        if(son[v] == -1 || size[e[i].to] > size[son[v]])
            son[v] = e[i].to;
    }
}

void dfs2(int v,int tp) {
    tid[v] = ++tim; top[v] = tp;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != son[v])
            dfs2(e[i].to,e[i].to);
}

#define lson o << 1
#define rson o << 1 | 1
int sum[N << 2],add[N << 2];

void build(int o,int l,int r)
{
    sum[o] = 0;
    if(l == r) return;
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
}

void pushdown(int o,int l,int r)
{
    if(add[o] != 0) {
        int mid = (l+r)>>1;
        add[lson] += add[o]; sum[lson] += (mid-l+1)*add[o];
        add[rson] += add[o]; sum[rson] += (r-mid)*add[o];
        add[o] = 0;
    }
}

void update(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) {add[o]++;sum[o]+=(r-l+1);return;}
    int mid = (l+r)>>1;
    pushdown(o,l,r);
    if(ll <= mid) update(lson,l,mid,ll,rr);
    if(rr > mid) update(rson,mid+1,r,ll,rr);
    sum[o] = sum[lson] + sum[rson];
}

void change(int x,int y)
{
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        update(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    update(1,1,n,tid[x],tid[y]);
}

int query(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r)return sum[o];
    int mid = (l+r)>>1,ans = 0;
    pushdown(o,l,r);
    if(ll <= mid) ans += query(lson,l,mid,ll,rr);
    if(rr > mid) ans += query(rson,mid+1,r,ll,rr);
    return ans;
}

int ask(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans += query(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    ans += query(1,1,n,tid[x],tid[y]);
    return ans;
}

int main()
{
    int q,u,v,z,tot = 0,now = 0;
    scanf("%d%d",&n,&q);
    init();
    for(int i = 2;i <= n;i++) {
        scanf("%d",&fa[i]); fa[i]++;
        add_edge(fa[i],i);
    }
    dfs1(1,0); dfs2(1,1);
    build(1,1,n);
    for(int i = 1;i <= q;i++) {
        scanf("%d%d%d",&u,&v,&z);
        u++;v++;z++;
        a[++tot].p = u-1,a[tot].num = i,a[tot].w = z,a[tot].f = -1;
        a[++tot].p = v,a[tot].num = i,a[tot].w = z,a[tot].f = 1;
    }
    sort(a+1,a+tot+1);
    for(int i = 1;i <= tot;i++) {
        while(now < a[i].p) {
            now++;
            change(1,now);
        }
        ans[a[i].num] += a[i].f * ask(1,a[i].w);
    }
    for(int i = 1;i <= q;i++) printf("%d\n",(ans[i]+mod) % mod);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值