2016.4.06Test:problem1:异或树:动态树的点分治

题目大意:给一棵树有点权有边权,每次操作可以修改一个点的点权,求所有的(a[x]^a[y])*dis(x,y)的和满足(x<y),点数<=30000,操作数<=30000,点权<=16384

20分暴力滚粗QAQ

做法:树分治。那个点权<=16384显然是2^14,这就提示我们用二进制分解

对于每次更新操作,可以认为是位数次更新,对于每个从1变成0的操作。我们减去树中那一位为0的到该点的路径之和,加上树中那一位为1的到该点的路径之和,从0变成1类似。

问题转换成每次查询所有值为x的点(x∈[0,1])到某个点的距离之和是多少并更新,这是可以通过点分治来做的。

在点分治中,对于每一层的树,我们记录其中一些点的信息(曾经被选作根的点)。需要记录的信息有以该点为根的子树中,0和1的数量,0和1到该点的距离之和。
当我们查询与修改时,只要在每层点分治树上做就可以了,为了避免重复计算我们记录根(g1)以及根的儿子(g2)的信息。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=30010;
int n,tot=1,N,h[maxn],dep[maxn],fa[maxn];
struct edge{int to,next,w;}G[maxn*10];
LL rec[maxn*2][15][2],len[maxn],l[maxn][15];
int g1[maxn][15],g2[maxn][15],s[maxn*2][15][2];
int size[maxn],dp[maxn],a[maxn],q[maxn];
bool flag[maxn];

void add(int M,int x){
	for (int i=0;i<=14;++i){
	    rec[M][i][(a[x]>>i)&1]+=len[x];
	    s[M][i][(a[x]>>i)&1]++;
	}
}

void dfs(int x,int D,int M){
	add(M,x); g2[x][D]=M;
	for (int i=h[x];i;i=G[i].next)
	    if (!flag[G[i].to]&&fa[G[i].to]==x)
	        dfs(G[i].to,D,M);
}

void bfs(int x,int D){
	int t=0,head=1; ++N;
	fa[x]=0; len[x]=0; q[1]=x;
	while (t<head){
		int now=q[++t]; 
		l[now][D]=len[now]; dep[now]=D;
		for (int i=h[now];i;i=G[i].next)
		    if (!flag[G[i].to]&&G[i].to!=fa[now]){
				q[++head]=G[i].to; fa[G[i].to]=now;
				len[G[i].to]=len[now]+G[i].w;
		    }
	}
	for (int i=1;i<=head;++i){
		g1[q[i]][D]=N;
		add(N,q[i]);
	}
	for (int i=h[x];i;i=G[i].next)
	    if (!flag[G[i].to])
	        dfs(G[i].to,D,++N);
}

int getroot(int u){
	int t=0,head=1; q[1]=u; fa[u]=0;
	while (t<head){
		int now=q[++t]; size[now]=1; dp[now]=0;
		for (int i=h[now];i;i=G[i].next)
		    if (!flag[G[i].to]&&G[i].to!=fa[now]){
				q[++head]=G[i].to;
				fa[G[i].to]=now;
		    }
	}
	for (int i=head;i>=2;--i){
		size[fa[q[i]]]+=size[q[i]];
		dp[fa[q[i]]]=max(dp[fa[q[i]]],size[q[i]]);
	}
	for (int i=2;i<=head;++i)
	    dp[q[i]]=max(dp[q[i]],size[q[1]]-size[q[i]]);
	int ret=q[1];
	for (int i=2;i<=head;++i)
	    if (dp[q[i]]<dp[ret]) ret=q[i];
	return ret;
}

void build(int x,int D){
	int root=getroot(x); bfs(root,D); flag[root]=1;
	for (int i=h[root];i;i=G[i].next)
	    if (!flag[G[i].to]) build(G[i].to,D+1);
}

void add_edge(int x,int y,int z){
	G[++tot].to=y;G[tot].next=h[x];h[x]=tot;G[tot].w=z;
}

LL ask(int x){
	LL ret=0;
	for (int i=0;i<=dep[x];++i)
	    for (int j=0;j<=14;++j){
			int t=(a[x]>>j)&1;
			ret+=rec[g1[x][i]][j][t^1]<<j;
			ret+=(1LL*s[g1[x][i]][j][t^1]*l[x][i])<<j;
			if (i<dep[x]){
				ret-=rec[g2[x][i]][j][t^1]<<j;
				ret-=(1LL*s[g2[x][i]][j][t^1]*l[x][i])<<j;
			}
	    }return ret;
}

void change(int u,int x){
	for(int i=0;i<=14;++i)
		if(((a[u]>>i)&1)!=((x>>i)&1))
	    for (int j=0;j<=dep[u];++j){
			int t=(a[u]>>i)&1,t1=g1[u][j],t2=g2[u][j];
			rec[t1][i][t]-=l[u][j];
			s[t1][i][t]--;
			rec[t1][i][t^1]+=l[u][j];
			s[t1][i][t^1]++;
			if (g2[u][j]){
				rec[t2][i][t]-=l[u][j];
				s[t2][i][t]--;
				rec[t2][i][t^1]+=l[u][j];
				s[t2][i][t^1]++;
			}
	    }
	a[u]=x;
}

int main(){
	scanf("%d",&n); N=1;
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	for (int i=1;i<n;++i){
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		add_edge(x,y,z); add_edge(y,x,z);
	}
	build(1,0);
	int T; scanf("%d",&T); LL ans=0;
	for (int i=1;i<=n;++i) ans+=ask(i);
	ans/=2; 
	for (int i=1;i<=T;++i){
		int x,y; scanf("%d%d",&x,&y);
		ans-=ask(x); change(x,y); ans+=ask(x);
		printf("%lld\n",ans);
	}
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值