【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\n",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\n",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\n",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

时间: 2024-12-23 08:16:26

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

BZOJ 2243:染色(树链剖分+区间合并线段树)

[SDOI2011]染色Description给定一棵有n个节点的无根树和m个操作,操作有2类:1.将节点a到节点b路径上所有点都染成颜色c:2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”.请你写一个程序依次完成这m个操作.Input第一行包含2个整数n和m,分别表示节点数和操作数:第二行包含n个正整数表示n个节点的初始颜色下面 行每行包含两个整数x和y,表示x和y之间有一条无向边.下面 行每行描述一个操作:“C

BZOJ 2243: [SDOI2011]染色 树链剖分 倍增lca 线段树

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/problem.php?id=2243 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写

【BZOJ4515】游戏,树链剖分+永久化标记线段树维护线段信息(李超线段树)

Time:2016.05.10 Author:xiaoyimi 转载注明出处谢谢 传送门 思路: 李超线段树 一开始听faebdc讲,并没有听的很懂ww 后来找到良心博文啊有木有 折越 首先可以把修改转换一下,因为那个dis非常不爽.显然s~t的路径有s~lca和lca~t组成.令d[x]表示x的深度,对于s~lca上面的点,修改的值相当于a*(d[s]-d[x])+b=-a*d[x]+(b-a*d[s]),lca~t上面的点的值相当于a*(d[s]+d[x]-2*d[lca])+b=a*d[x

BZOJ 3589 动态树 树链剖分+容斥定理

题目大意:给出一棵树,每一个节点有一个权值,一开始所有节点的权值都是0.有两种操作,0 x y代表以x为根节点的子树上所有点的权值增加y.1 k a1 b1 a2 b2 --ak bk代表询问.一共有k条边( k <= 5),这些边保证是一个点到根节点的路径上的一段.问这些路径段上的点的权值和是多少,可能有多条路径重叠的情况. 思路:子树修改,区间查询,很明显用树链剖分解决,树链剖分维护一个size域,那么x的子树的范围就是pos[x]到pos[x] + size[x] - 1这一段上,可以用线

BZOJ 3589 动态树 树链剖分+容斥原理

题目大意:给定一棵以1为根的有根树,每个节点有点权,提供两种操作: 1.以某个节点为根的子树所有节点权值+x 2.求一些链的并集的点权和,其中这些链都是由某个节点出发指向根 首先子树修改,链上查询,树链剖分的WT~ 然后这些链上的每个点的点权都只能被加一次,肯定不能打标记,由于k<=5,我们考虑容斥原理 总权值=单链-两两之交+三链之交-- 状压枚举即可 两条链的交集求法如下: 1.求两条链底的LCA 2.若LCA的深度小于其中一条链的链顶深度 交集为空 返回0 3.返回一条链 链底为LCA 链

BZOJ 3589 动态树 树链拆分+纳入和排除定理

标题效果:鉴于一棵树.每个节点有一个右值,所有节点正确启动值他们是0.有两种操作模式,0 x y代表x右所有点的子树的根值添加y. 1 k a1 b1 a2 b2 --ak bk代表质疑. 共同拥有者k边缘( k <= 5),这些边保证是一个点到根节点的路径上的一段. 问这些路径段上的点的权值和是多少,可能有多条路径重叠的情况. 思路:子树改动,区间查询,非常明显用树链剖分解决,树链剖分维护一个size域.那么x的子树的范围就是pos[x]到pos[x] + size[x] - 1这一段上.能够

【树链剖分(区间线段树)】BZOJ4196-[NOI2015]软件包管理

[题目大意] 如果软件包A依赖软件包B,那么安装软件包A以前,必须先安装软件包B.同时,如果想要卸载软件包B,则必须卸载软件包A.而且,由于你之前的工作,除0号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而0号软件包不依赖任何一个软件包.依赖关系不存在环.求出在安装和卸载某个软件包时,实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包.(注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安

【块状树】【树链剖分】【线段树】bzoj3531 [Sdoi2014]旅行

离线后以宗教为第一关键字,操作时间为第二关键字排序. <法一>块状树,线下ac,线上tle…… #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<queue> using namespace std; queue<int>q; int f,c; inline void R(int &x){ c=0;f=1;

codeforces 343D Water Tree 树链剖分 dfs序 线段树 set

题目链接 这道题主要是要考虑到同一棵子树中dfs序是连续的 然后我就直接上树剖了... 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=600005; 4 5 struct Node 6 { 7 int l,r; 8 int value; 9 void init() 10 { 11 l=r=value=0; 12 } 13 }tree[4*MAXN]; 14 vector<int>nei[MAXN]

[GDOI2016] 疯狂动物园 [树链剖分+可持久化线段树]

题面 太长了,而且解释的不清楚,我来给个简化版的题意: 给定一棵$n$个点的数,每个点有点权,你需要实现以下$m$个操作 操作1,把$x$到$y$的路径上的所有点的权值都加上$delta$,并且更新一个版本 操作2,对于有向路径$(x,y)$上的点$a_i$,求下面的和值: $\sum_{i=1}^{len} a_i \sum_{j=1}^{len-i} j$ 操作3,回到第$i$个版本(但是下一次更新以后还是到总版本号+1的那个版本) 思路 这个题显然一眼就是树剖+可持久化数据结构啊 那么核心