[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的那个版本)

思路

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

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

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

这个会让我们很难做

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

$\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}((n^2+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];
        }
    }
}

原文地址:https://www.cnblogs.com/dedicatus545/p/9534904.html

时间: 2024-11-07 22:29:13

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

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

[BZOJ3681]Arietta Description Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中.但是她从未停止过和恋人 Velding 的书信往来.一天,她准备去探访他.对着窗外的阳光,临行前她再次弹起了琴.她的琴的发声十分特殊.让我们给一个形式化的定义吧.所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 Hi .Arietta 有 m 个力度,第 i 个力度能弹出 Di 节点的子树中,音高在 [Li,R

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

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

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

【树链剖分】【线段树】bzoj3626 [LNOI2014]LCA

引用题解: http://blog.csdn.net/popoqqq/article/details/38823457 题目大意: 给出一个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的最近公共祖先的深度之和) 这题看见了直接卡壳...然后

【块状树】【树链剖分】【线段树】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]

POJ 2763 Housewife Wind(树链剖分)(线段树单点修改)

Housewife Wind Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 10378   Accepted: 2886 Description After their royal wedding, Jiajia and Wind hid away in XX Village, to enjoy their ordinary happy life. People in XX Village lived in beauti