zoukankan      html  css  js  c++  java
  • [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_iast 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
    ",lastans=ask(t1,t2));
    		}
    		if(tt==3){
    			t1=read();t1^=lastans;
    			now=root[t1];
    		}
    	}
    }
    
  • 相关阅读:
    理解 Java 构造函数不可以继承
    jsp页面表单还可以声明为date类型
    数据返回(数据共享,即从后端返回到前端调用,四种(requesst、ModelAndView、Model、Map))
    SpringMVC实现Action的两种方式以及与Struts2的区别
    视图解析器InternalResourceViewResolver在什么情况下需要配置?在什么情况下不需要配置?
    日期格式转换
    【SpringMVC框架】非注解的处理器映射器和适配器
    springmvc中的web.xml配置(包含中文乱码解决)
    Spring 7大功能模块的作用[转]
    python
  • 原文地址:https://www.cnblogs.com/dedicatus545/p/9534904.html
Copyright © 2011-2022 走看看