20201031B组 T3 生命之树

jzoj5363

题目大意

定义
a n s u = ∑ i ∈ d e c u ∑ j ∈ d e c u , i < j ( v a l i ⊕ v a l j ) × w ( S i , S j ) ans_u=\sum_{i \in dec_u}\sum_{j \in dec_u,i<j}(val_i \oplus val_j)\times w(S_i,S_j) ansu=idecujdecu,i<j(valivalj)×w(Si,Sj)
其中 ⊕ \oplus 为异或, d e c u dec_u decu表示 u u u的子树, w ( S i , S j ) w(S_i,S_j) w(Si,Sj)表示字符串 S i S_i Si S j S_j Sj的最大公共前缀

求树上每个点对应的 a n s u ans_u ansu

TJ
前置知识

w ( S i , S j ) w(S_i , S_j) w(Si,Sj)显然可以用Trie维护,异或操作可以按位拆开计算答案。

方法一

考虑对每个点维护一棵Trie,于是将儿子的Trie合并时就可以计算答案

方法二

树上启发式合并

只维护一棵Trie,每到一个节点先递归计算其轻儿子的答案,计算前后要清空Trie,然后计算重儿子的答案,保留Trie,再将其他儿子的节点一一插入Trie中并计算答案即可

#include<bits/stdc++.h>
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define fd(i,a,b) for(ll i=a;i>=b;i--)
#define ll long long
const ll INF=1e9+7;
using namespace std;
const ll N=1e5;
const ll SZ=5e5;
const ll M=20;
char str[SZ+10];
ll l[N+10],v[N+10];
namespace TRIE{
	int ch[SZ+10][30],s[SZ+10][30],a[SZ+10],cnt;
	void init(ll t){
		memset(ch[t],0,sizeof(ch[t]));
		memset(s[t],0,sizeof(s[t]));
		a[t]=0;
	}
	ll add(ll t){
		ll x=1,sum=0;
		fo(i,l[t],l[t+1]-1){
			if(!ch[x][str[i]-'a']){
				ch[x][str[i]-'a']=++cnt;
				init(cnt);
			}
			x=ch[x][str[i]-'a'];
			fo(j,0,M){
				if((1<<j)&v[t]){
					sum+=(a[x]-s[x][j])*(1ll<<j);
				}else{
					sum+=s[x][j]*(1ll<<j);
				}
			}
			a[x]++;
			fo(j,0,M){
				if((1ll<<j)&v[t])s[x][j]++;
			}
		}
		return sum;
	}
}
struct edge{
	ll st,en,next;
}E[N*2+10];
ll n,tot,last[N+10],X,Y,fa[N+10],son[N+10],size[N+10];
ll len,ans[N+10],cnt,t[N+10],dfn[N+10];
void add(ll x,ll y){
	E[++tot]=(edge){x,y,last[x]};
	last[x]=tot;
}
void dfs1(ll x){
	dfn[x]=++cnt;
	t[cnt]=x;
	size[x]=1;
	for(ll p=last[x];p;p=E[p].next){
		ll y=E[p].en;
		if(y==fa[x])continue;
		fa[y]=x;
		dfs1(y);
		size[x]+=size[y];
		if(size[y]>size[son[x]])son[x]=y;
	}
}
void dfs2(ll x){
	for(ll p=last[x];p;p=E[p].next){
		ll y=E[p].en;
		if(y==fa[x] || y==son[x])continue;
		TRIE::cnt=1;
		TRIE::init(1);
		dfs2(y);
//		ans[x]+=ans[y];
	}
	TRIE::cnt=1;
	TRIE::init(1);
	if(son[x]){
		dfs2(son[x]);
		ans[x]+=ans[son[x]];
		for(ll p=last[x];p;p=E[p].next){
			ll y=E[p].en;
			if(y==fa[x] || y==son[x])continue;
			fo(i,dfn[y],dfn[y]+size[y]-1){
				ans[x]+=TRIE::add(t[i]);
			}
		}
	}
	ans[x]+=TRIE::add(x);
}
int main(){
//	freopen("in.txt","r",stdin);
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%lld",&n);
	fo(i,1,n)scanf("%lld",&v[i]);
	fo(i,1,n){
		l[i]=len+1;
		scanf("%s",str+len+1);
		len=strlen(str+1);
	}
	l[n+1]=len+1;
	fo(i,2,n){
		scanf("%lld%lld",&X,&Y);
		add(X,Y);add(Y,X);
	}
	dfs1(1);
	dfs2(1);
	fo(i,1,n)printf("%lld\n",ans[i]);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值