题面

太长了,而且解释的不清楚,我来给个简化版的题意:

给定一棵$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的那个版本)

思路

这个题显然一眼就是树剖+可持久化数据结构啊

那么核心问题就是怎么用数据结构求上面那个和值

我们发现一个问题:上面那个东西里面,路径上最后一个点是没有贡献的

这个会让我们很难做

因此我们把这个式子加上一个东西再减掉,变成这个样子

$\sum_{i=1}^{len} a_i\ast S(len-i+1)-\sum_{i=1}^{len}(n-i+1)\ast a_i$

其中$S(i)$表示自然数求和

这样子就不用先把最后一个节点去掉了

然后我们把前面那个式子里面的$S$展开,发现得到这样的一个东西

$\sum_{i=1}^{len} a_i \frac{1}{2} (len^2-2leni + i^2 +3len-3i +2)$

我们把后面括号里的东西根据$i$的次数分类,得到下面的变换后式子

$\frac{1}{2}((n2+3n+2)\sum_{i=1}n a_i +(-2n-3)\sum_{i=1}^n ia_i + \sum_{i=1}^n i^2a_i)$

因为$n$是固定的,所以我们可以用线段树维护三个$\sum$里面的东西,并且用标记永久化来实现区间修改

然后其实这道题目就做完了

但是此题写起来极其恶心,因为你会发现你需要正着维护三个,还要反着维护3个

在询问的时候,$lca$两边的树链的正反是不一样的

标记永久化的时候标记上面的每一个节点收到的影响是不一样大的

等等恶心的问题

所以这实际上是一道毒瘤题,考场上遇到了千万别写

Code

比较复杂,我做了注释,可以参考一下

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cassert>
#define ll long long
#define MOD 20160501
#define inv2 10080251
ll Sum2[100010];
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
ll qpow(ll a,ll b){
ll re=1;
while(b){
if(b&1) re=re*a%MOD;
a=a*a%MOD;b>>=1;
}
return re;
}
//树链剖分
int n,first[100010],ori[100010],dep[100010],size[100010],son[100010],top[100010],pos[100010],back[100010],fa[100010];
struct edge{
int to,next;
}e[200010];int cnte,clk;
inline void addedge(int u,int v){
e[++cnte]=(edge){v,first[u]};first[u]=cnte;
e[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void dfs1(int u,int f){
int i,v;
dep[u]=dep[f]+1;
fa[u]=f;
size[u]=1;
for(i=first[u];~i;i=e[i].next){
v=e[i].to;if(v==f) continue;
dfs1(v,u);
size[u]+=size[v];
if(size[son[u]]<size[v]) son[u]=v;
}
}
void dfs2(int u,int t){
int i,v;
top[u]=t;
clk++;
pos[u]=clk;
back[clk]=u;
if(son[u]) dfs2(son[u],t);
for(i=first[u];~i;i=e[i].next){
v=e[i].to;if(v==son[u]||v==fa[u]) continue;
dfs2(v,v);
}
}
//segment tree with lazy tag stablized
ll add[12000010];int ch[12000010][2],root[200010],now,cnt,tot;
struct node{
ll sum0,sum1,sum2,siz;
node(ll s0=0,ll s1=0,ll s2=0,ll s=0){sum0=s0;sum1=s1;sum2=s2;siz=s;}
bool empty(){return (sum1==0&&sum2==0&&sum0==0&&siz==0);}
}a[12000010],rev[12000010];
ll sqr(ll x){
return x*x%MOD;
}
ll S1(ll x){自然数求和
return x*(x+1)%MOD*inv2%MOD;
}
ll S2(ll x){//这里是自然数平方的前缀和
return Sum2[x];
}
void merge(node &cur,node l,node r){//合并两个点
if(l.empty()){cur=r;return;}
if(r.empty()){cur=l;return;}
cur.siz=l.siz+r.siz;
cur.sum0=(l.sum0+r.sum0)%MOD;
cur.sum1=(l.sum1+r.sum1+r.sum0*l.siz)%MOD;
cur.sum2=(l.sum2+r.sum2+r.sum1*2*l.siz+r.sum0*sqr(l.siz))%MOD;
}
int build(int l,int r){
int cur=++cnt;
if(l==r){
a[cur].siz=rev[cur].siz=1;
a[cur].sum0=a[cur].sum1=a[cur].sum2=ori[back[l]];
rev[cur].sum0=rev[cur].sum1=rev[cur].sum2=ori[back[l]];
return cur;
}
int mid=(l+r)>>1;
ch[cur][0]=build(l,mid);
ch[cur][1]=build(mid+1,r);
merge(a[cur],a[ch[cur][0]],a[ch[cur][1]]);
merge(rev[cur],rev[ch[cur][1]],rev[ch[cur][0]]);
return cur;
}
node get0(int l,int r,int ql,int qr,int cur,ll tmp){
node re;
if(l>=ql&&r<=qr){
re=a[cur];
(re.sum0+=re.siz*tmp)%=MOD;
(re.sum1+=S1(re.siz)*tmp)%=MOD;
(re.sum2+=S2(re.siz)*tmp)%=MOD;
return re;
}
int mid=(l+r)>>1;
if(mid>=qr) return get0(l,mid,ql,qr,ch[cur][0],tmp+add[cur]);
else{
if(mid<ql) return get0(mid+1,r,ql,qr,ch[cur][1],tmp+add[cur]);
else{
merge(re,get0(l,mid,ql,qr,ch[cur][0],tmp+add[cur]),get0(mid+1,r,ql,qr,ch[cur][1],tmp+add[cur]));
}
}
return re;
}
node get1(int l,int r,int ql,int qr,int cur,ll tmp){
node re;
if(l>=ql&&r<=qr){
re=rev[cur];
(re.sum0+=re.siz*tmp)%=MOD;
(re.sum1+=S1(re.siz)*tmp)%=MOD;
(re.sum2+=S2(re.siz)*tmp)%=MOD;
return re;
}
int mid=(l+r)>>1;
if(mid>=qr) return get1(l,mid,ql,qr,ch[cur][0],tmp+add[cur]);
else{
if(mid<ql) return get1(mid+1,r,ql,qr,ch[cur][1],tmp+add[cur]);
else{
merge(re,get1(mid+1,r,ql,qr,ch[cur][1],tmp+add[cur]),get1(l,mid,ql,qr,ch[cur][0],tmp+add[cur]));
}
}
return re;
}
int change(int l,int r,int ql,int qr,int pre,int val){
int cur=++cnt;
a[cur]=a[pre];rev[cur]=rev[pre];
ch[cur][0]=ch[pre][0];ch[cur][1]=ch[pre][1];add[cur]=add[pre];
//以下是更新值的部分
(a[cur].sum0+=(qr-ql+1)*val)%=MOD;
(rev[cur].sum0+=(qr-ql+1)*val)%=MOD;
(a[cur].sum1+=val*(S1(qr-l+1)-S1(ql-l)+MOD))%=MOD;
(rev[cur].sum1+=val*(S1(r-ql+1)-S1(r-qr)+MOD))%=MOD;
(a[cur].sum2+=val*(S2(qr-l+1)-S2(ql-l)+MOD))%=MOD;
(rev[cur].sum2+=val*(S2(r-ql+1)-S2(r-qr)+MOD))%=MOD;
if(l==ql&&r==qr){
(add[cur]+=val)%=MOD;
return cur;
}
int mid=(l+r)>>1;
if(mid>=ql) ch[cur][0]=change(l,mid,ql,min(mid,qr),ch[pre][0],val);
if(mid<qr) ch[cur][1]=change(mid+1,r,max(mid+1,ql),qr,ch[pre][1],val);
//标记永久化不需要update
return cur;
}
ll ask(int l,int r){
node re0(0,0,0,0),re1(0,0,0,0),rev0(0,0,0,0),rev1(0,0,0,0),tmp;int p=1;
//pos==1: l=l,r=r
//pos==0: l=r,r=l
//这里是因为树剖的过程中会交换lr,所以记录一下,上面的re表示正常算的,rev是因为原式最后还有一个第二种求和,但是方向相反,因此反着的答案也要记录
while(top[l]!=top[r]){
if(dep[top[l]]>dep[top[r]]) swap(l,r),p^=1;
if(!p){
tmp=get1(1,n,pos[top[r]],pos[r],now,0);
merge(re0,re0,tmp);
tmp=get0(1,n,pos[top[r]],pos[r],now,0);
merge(rev0,tmp,rev0);
}
else{
tmp=get0(1,n,pos[top[r]],pos[r],now,0);
merge(re1,tmp,re1);
tmp=get1(1,n,pos[top[r]],pos[r],now,0);
merge(rev1,rev1,tmp);
}
r=fa[top[r]];
}
if(dep[l]>dep[r]) swap(l,r),p^=1;
if(p){
merge(re0,re0,get0(1,n,pos[l],pos[r],now,0));
merge(re1,re0,re1);
merge(rev0,get1(1,n,pos[l],pos[r],now,0),rev0);
merge(rev1,rev1,rev0);
}
else{
merge(re1,get1(1,n,pos[l],pos[r],now,0),re1);
merge(re1,re0,re1);
merge(rev1,rev1,get0(1,n,pos[l],pos[r],now,0));
merge(rev1,rev1,rev0);
}
return (((sqr(re1.siz)+3*re1.siz+2)*inv2%MOD*re1.sum0%MOD)-((2ll*re1.siz+3)*inv2%MOD*re1.sum1%MOD)+(inv2*re1.sum2%MOD)-rev1.sum1+MOD*2)%MOD;
//这里就是统计答案,方法和上面的题解中的公式是一样的
}
void modify(int l,int r,int val){//链上修改
int pre=now;
while(top[l]!=top[r]){
if(dep[top[l]]>dep[top[r]]) swap(l,r);
pre=change(1,n,pos[top[r]],pos[r],pre,val);
r=fa[top[r]];
}
if(dep[l]>dep[r]) swap(l,r);
root[++tot]=change(1,n,pos[l],pos[r],pre,val);
now=root[tot];
}
int main(){
memset(first,-1,sizeof(first));
ll i,t1,t2,t3,tt;ll lastans=0;
Sum2[0]=0;
for(i=1;i<=100000;i++) Sum2[i]=(Sum2[i-1]+i*i)%MOD;
n=read();int Q=read();
for(i=1;i<n;i++){
t1=read(),t2=read();
addedge(t1,t2);
}
for(i=1;i<=n;i++) ori[i]=read();
dfs1(1,0);dfs2(1,1);
now=root[0]=build(1,n);
while(Q--){
tt=read();
if(tt==1){
t1=read();t2=read();t3=read();
t1^=lastans;t2^=lastans;
modify(t1,t2,t3);
}
if(tt==2){
t1=read();t2=read();
t1^=lastans;t2^=lastans;
printf("%lld\n",lastans=ask(t1,t2));
}
if(tt==3){
t1=read();t1^=lastans;
now=root[t1];
}
}
}

[GDOI2016] 疯狂动物园 [树链剖分+可持久化线段树]的更多相关文章

  1. BZOJ4012[HNOI2015]开店——树链剖分+可持久化线段树/动态点分治+vector

    题目描述 风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到 人生哲学.最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱.这样的 想法当然非常好啦,但是她们也发现她们面临着一个 ...

  2. 【BZOJ3681】Arietta 树链剖分+可持久化线段树优化建图+网络流

    [BZOJ3681]Arietta Description Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中.但是她从未停止过和恋人 Velding 的书信往来.一 ...

  3. 【BZOJ4704】旅行 树链剖分+可持久化线段树

    [BZOJ4704]旅行 Description 在Berland,有n个城堡.每个城堡恰好属于一个领主.不同的城堡属于不同的领主.在所有领主中有一个是国王,其他的每个领主都直接隶属于另一位领主,并且 ...

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

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/pr ...

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

    [SDOI2011]染色Description给定一棵有n个节点的无根树和m个操作,操作有2类:1.将节点a到节点b路径上所有点都染成颜色c:2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认 ...

  6. HDU 5029 Relief grain 树链剖分打标记 线段树区间最大值

    Relief grain Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid= ...

  7. 【Codeforces】【网络流】【树链剖分】【线段树】ALT (CodeForces - 786E)

    题意 现在有m个人,每一个人都特别喜欢狗.另外还有一棵n个节点的树. 现在每个人都想要从树上的某个节点走到另外一个节点,且满足要么这个人自带一条狗m,要么他经过的所有边h上都有一条狗. 2<=n ...

  8. LOJ2269 [SDOI2017] 切树游戏 【FWT】【动态DP】【树链剖分】【线段树】

    题目分析: 好题.本来是一道好的非套路题,但是不凑巧的是当年有一位国家集训队员正好介绍了这个算法. 首先考虑静态的情况.这个的DP方程非常容易写出来. 接着可以注意到对于异或结果的计数可以看成一个FW ...

  9. HYSBZ 4034 【树链剖分】+【线段树 】

    <题目链接> 题目大意: 有一棵点数为 N 的树,以点 1 为根,且树点有权值.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x ...

随机推荐

  1. BZOJ1050: [HAOI2006]旅行comf(并查集 最小生成树)

    Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4021  Solved: 2257[Submit][Status][Discuss] Descript ...

  2. Hibernate进阶学习3

    Hibernate进阶学习3 测试hibernate的多表关联操作(一对多,多对一,多对多) 表之间的关系主要在类与元数据配置文件中体现 package com.hibernate.domain; i ...

  3. MySQL的where条件优化

    where 条件优化  适合select delete update   1.避免无用的括号 ((a AND b) AND c OR (((a AND b) AND (c AND d)))) -> ...

  4. 制作linux系统U盘并使用U盘安装CentOS7.6系统

    目录   一.制作linux启动盘     1.1. 准备工作     1.2. 制作linux系统U盘   二.使用U盘安装Centos7.6     2.1. 使用U盘启动     2.2. 更改 ...

  5. Java NIO 教程

    Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.本系列教程将有助于你学习和理解Java NIO. Java NIO提供了与 ...

  6. http虚拟主机的简单配置训练

    http的虚拟主机 对于某些web访问站点而言,每天的访问量很少,因此真正的放一台服务器去进行web站点是很 浪费资源的,因此我们选择了虚拟主机 web处理模块的分类(MPM) 1.perfork 一 ...

  7. Teaching Is a Fruitful Way to Learn【教学是一种有效的学习方式】

    Teaching Is a Fruitful Way to Learn For thousands of years, people have known that the best way to u ...

  8. [CodeForces948C]Producing Snow(优先队列)

    Description 题目链接 Solution 将温度做一个前缀和,用一个优先队列依次处理一遍 思路还是很简单的 Code #include <cstdio> #include < ...

  9. python-4函数式编程

    1-高阶函数 变量可以指向函数.   def add(x, y, f): 例如f参数为函数 编写高阶函数,就是让函数的参数能够接收别的函数. Python内建了map()和reduce()高阶函数. ...

  10. 財務会計管理(FI&CO)

    FI(財務会計)系のSAP DBテーブル.随時更新していきます. [勘定コードマスタ]SKA1: 勘定コードマスタ(勘定コード表データ)SKB1: 勘定コードマスタ(会社コードデータ)SKAT: テキ ...