【BZOJ-3589】动态树 树链剖分 + 线段树 + 线段覆盖(特殊的技巧)

3589: 动态树

Time Limit: 30 Sec  Memory Limit: 1024 MB
Submit: 405  Solved: 137
[Submit][Status][Discuss]

Description

别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件
事件0:
这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子.
事件1:
小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和. 注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次.

Input

第一行一个整数n(1<=n<=200,000), 即节点数.
接下来n-1行, 每行两个数字u, v. 表示果子u和果子v之间有一条直接的边. 节点从1开始编号.
在接下来一个整数nQ(1<=nQ<=200,000), 表示事件.
最后nQ行, 每行开头要么是0, 要么是1.
如果是0, 表示这个事件是事件0. 这行接下来的2个整数u, delta表示以u为根的子树中的每个节点长出了delta个果子.
如果是1, 表示这个事件是事件1. 这行接下来一个整数K(1<=K<=5), 表示这次询问涉及K个树枝. 接下来K对整数u_k, v_k, 每个树枝从节点u_k到节点v_k. 由于果子数可能非常多, 请输出这个数模2^31的结果.

Output

对于每个事件1, 输出询问的果子数.

Sample Input

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

Sample Output

13

HINT

 1 <= n <= 200,000, 1 <= nQ <= 200,000, K = 5.

生成每个树枝的过程是这样的:先在树中随机找一个节点, 然后在这个节点到根的路径上随机选一个节点, 这两个节点就作为树枝的两端.

Source

By 佚名提供

Solution

真丶动态树,直接用树链剖分水过

首先树链剖分不解释,子树修改?水过

在于查询多段路径的并。

一开始想的是这不直接覆盖么...然后水水的一波..打完发现哦,这是树,不能这么搞...

难道真的要写LCT?其实不用...

思考可以在路径上的点打标记,沿着有标记的路径求和?似乎可以,但没实现

对于路径的求并,可以考虑容斥原理,应该很优,但是不会TAT

所以是某奇怪的姿势:

线段覆盖!详见CodeVS线段覆盖..

很显然,对于树链剖分,是把树上的点映射到序列上,那么对于路径的询问其实就是路径上的多段区间的询问

多次询问就是多次多段区间的询问,考虑直接把这些区间拿出来,然后利用线段覆盖的姿势合并这些区间,对合并后的统计答案即可

这里结果会很大,所以可以让结果自然溢出,最后结果&MaxInt可以变回正数,int的自然溢出是在$-2^{31}-0$循环,unsignedint的是在$-2^{32}-0$循环

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#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 210000
int n,q;
struct Edgenode{int to,next;}edge[maxn<<1];
int head[maxn],cnt=1;
void add(int u,int v){cnt++; edge[cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt;}
void insert(int u,int v){add(u,v); add(v,u);}
//
//int fa[maxn],son[maxn],deep[maxn],size[maxn],pl[maxn],sz,pr[maxn],pre[maxn],top[maxn];
int fa[maxn],top[maxn],son[maxn];
int size[maxn],pl[maxn],pr[maxn],sz,pre[maxn],deep[maxn];
void dfs_1(int now)
{
    size[now]=1;
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=fa[now])
            {
                fa[edge[i].to]=now;
                deep[edge[i].to]=deep[now]+1;
                dfs_1(edge[i].to);
                if (size[son[now]]<size[edge[i].to]) son[now]=edge[i].to;
                size[now]+=size[edge[i].to];
            }
}
void dfs_2(int now,int chain)
{
    pl[now]=++sz;pre[sz]=now;top[now]=chain;
    if (son[now]) dfs_2(son[now],chain);
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=son[now] && edge[i].to!=fa[now])
            dfs_2(edge[i].to,edge[i].to);
    pr[now]=sz;
}
//
struct Treenode{int l,r,size,sum,tag;}tree[maxn<<3];
void update(int now)
{
    tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum;
}
void pushdown(int now)
{
    if (tree[now].tag)
        {
            tree[now<<1].tag+=tree[now].tag;tree[now<<1|1].tag+=tree[now].tag;
            tree[now<<1].sum+=tree[now].tag*tree[now<<1].size; 
            tree[now<<1|1].sum+=tree[now].tag*tree[now<<1|1].size;
            tree[now].tag=0;
        }
}
void build(int now,int l,int r)
{
    tree[now].l=l; tree[now].r=r; tree[now].size=r-l+1;
    tree[now].tag=0; tree[now].sum=0;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(now<<1,l,mid); build(now<<1|1,mid+1,r);
    update(now);
}
void change(int now,int L,int R,int D)
{
    pushdown(now);
    if (L<=tree[now].l && R>=tree[now].r) 
        {tree[now].sum+=D*tree[now].size; tree[now].tag+=D; return;}
    int mid=(tree[now].l+tree[now].r)>>1;
    if (L<=mid) change(now<<1,L,R,D);
    if (mid<R) change(now<<1|1,L,R,D);
    update(now);
}
int query(int now,int L,int R)
{
    pushdown(now);
    if (L<=tree[now].l && R>=tree[now].r) return tree[now].sum;
    int mid=(tree[now].l+tree[now].r)>>1,ans=0;
    if (L<=mid) ans+=query(now<<1,L,R);
    if (mid<R) ans+=query(now<<1|1,L,R);
//    printf("%d
",ans); puts("OK");
    return ans;
}
//
struct Linenode
{    
    int u,v;
    bool operator < (const Linenode & A) const
        {return u<A.u;}
}line[maxn]; int ln;
void AddLine(int x,int y)
{
    ln++;line[ln].u=x;line[ln].v=y;
}
void GetLine(int x,int y)
{
    while (top[x]!=top[y])
        {
            if (deep[top[x]]<deep[top[y]]) swap(x,y);
            AddLine(pl[top[x]],pl[x]);
            x=fa[top[x]];
        }
    if (deep[x]>deep[y]) swap(x,y);
    AddLine(pl[x],pl[y]);
}
void Ask()
{
    sort(line+1,line+ln+1);
    int L=line[1].u,R=line[1].v,ans=0;
    for (int i=2; i<=ln; i++)
        if (line[i].u>R) ans+=query(1,L,R),L=line[i].u,R=line[i].v;
            else R=max(R,line[i].v);
    ans+=query(1,L,R);
    printf("%d
",ans&2147483647);
}
//
int main()
{
//    freopen("data.in","r",stdin);
//    freopen("data.out","w",stdout);
    n=read();
    for (int u,v,i=1; i<=n-1; i++) u=read(),v=read(),insert(u,v);
    dfs_1(1); dfs_2(1,1); build(1,1,sz); 
//    for (int i=1; i<=n; i++) printf("%d %d %d %d %d
",i,pl[i],pr[i],top[i],son[i]);
    q=read();
    for (int i=1; i<=q; i++)
        {    
            int opt=read(),a,b,k;
            if (opt==0) {a=read(),b=read(),change(1,pl[a],pr[a],b);continue;}
            ln=0; k=read();
            for (int j=1; j<=k; j++) 
                a=read(),b=read(),GetLine(a,b);
            Ask();
        }
    return 0;
} 

一个智障的Interesting的故事:调了2h+的题,一直WA,后来发现提交时一直忘关文件,于是妥A了.....Let's  ORZ  YveH

【BZOJ-3589】动态树      树链剖分 + 线段树 + 线段覆盖(特殊的技巧)