【洛谷P6072 [MdOI2020] Path】【回滚莫队+Trie】

题意

给一棵 n n n个节点的树,边有边权。定义一条路径的权值为边权的异或和。找两条节点不相交的路径,使得这两条路径的权值和最大。
n ≤ 30000 n\le 30000 n30000

分析

问题可以转化成对于每个点,求在该点的子树内和子树外分别找两个数,使得它们的异或的和尽可能大。
求出dfs序并倍增,就可以转化成在区间中选两个数,使得这两个数的异或和尽可能大。
用回滚莫队+Trie就好了。
回滚莫队具体来讲,就是左右端点都在同一个块内的询问直接暴力搞。把其他询问以左端点所在的块为第一关键字,右端点为第二关键字排序。在处理某个块内的询问的时候,记录 a n s ans ans表示该块的右端点到右指针的答案。每次把右指针推到询问的右端点,再把左指针从块的右端点推到询问的左端点,之后把左指针恢复就好了。
时间复杂度 O ( n n log ⁡ V ) O(n\sqrt n\log V) O(nn logV).

代码

#include<bits/stdc++.h>
#define pb push_back

const int N=30005;

int n,cnt,last[N],bel[N],tim,dfn[N],val[N],w[N*2],mx[N],sz,tot,B,bin[35],ans[N],rt;
struct edge{int to,next,w;}e[N*2];
struct data{int l,r,id;}q[N*2];
struct tree{int l,r,s;}t[N*30];
std::vector<int> vec[N];

void addedge(int u,int v,int w)
{
	e[++cnt].to=v;e[cnt].w=w;e[cnt].next=last[u];last[u]=cnt;
	e[++cnt].to=u;e[cnt].w=w;e[cnt].next=last[v];last[v]=cnt;
}

void dfs(int x,int fa)
{
	dfn[x]=++tim;
	bel[tim]=x;
	for (int i=last[x];i;i=e[i].next)
		if (e[i].to!=fa) val[e[i].to]=val[x]^e[i].w,dfs(e[i].to,x);
	mx[x]=tim;
}

int cmp(int x,int y)
{
	return q[x].r<q[y].r;
}

int get(int x)
{
	return (x+B-1)/B;
}

void ins(int &x,int d,int w,int c)
{
	if (!x) x=++sz;
	t[x].s+=c;
	if (d<0) return;
	if (w&bin[d]) ins(t[x].r,d-1,w,c);
	else ins(t[x].l,d-1,w,c);
}

int query(int x,int d,int w)
{
	if (d<0) return 0;
	if (w&bin[d])
		if (t[t[x].l].s) return query(t[x].l,d-1,w)+bin[d];
		else return query(t[x].r,d-1,w);
	else
		if (t[t[x].r].s) return query(t[x].r,d-1,w)+bin[d];
		else return query(t[x].l,d-1,w);
}

int calc(int l,int r)
{
	int mx=0;
	for (int i=l;i<=r;i++) mx=std::max(mx,query(rt,30,w[i])),ins(rt,30,w[i],1);
	for (int i=l;i<=r;i++) ins(rt,30,w[i],-1);
	return mx;
}

void pre()
{
	B=sqrt(n*2);
	for (int i=1;i<=n;i++) w[i]=w[i+n]=val[bel[i]];
	for (int i=2;i<=n;i++)
	{
		int l=dfn[i],r=mx[i],pos=get(l);
		if (get(l)==get(r)) ans[i]+=calc(l,r);
		else q[++tot].l=l,q[tot].r=r,q[tot].id=i,vec[pos].pb(tot);
		l=mx[i]+1;r=dfn[i]+n-1;pos=get(l);
		if (get(l)==get(r)) ans[i]+=calc(l,r);
		else q[++tot].l=l,q[tot].r=r,q[tot].id=i,vec[pos].pb(tot);
	}
	for (int i=1;i<=get(n*2);i++) std::sort(vec[i].begin(),vec[i].end(),cmp);
}

void solve()
{
	for (int i=1;i<=get(n*2);i++)
	{
		int r=B*i-1,mx=0;
		for (int j=0;j<vec[i].size();j++)
		{
			int now=vec[i][j],tmp=0;
			while (r<q[now].r) mx=std::max(mx,query(rt,30,w[++r])),ins(rt,30,w[r],1);
			int l=B*i;
			while (l>q[now].l) tmp=std::max(tmp,query(rt,30,w[--l])),ins(rt,30,w[l],1);
			while (l<B*i) ins(rt,30,w[l++],-1);
			ans[q[now].id]+=std::max(mx,tmp);
		}
		while (r>B*i-1) ins(rt,30,w[r--],-1);
	}
}

int main()
{
	bin[0]=1;
	for (int i=1;i<=30;i++) bin[i]=bin[i-1]*2;
	scanf("%d",&n);
	for (int i=1;i<n;i++)
	{
		int x,y,z;scanf("%d%d%d",&x,&y,&z);
		addedge(x,y,z);
	}
	dfs(1,0);
	pre();
	solve();
	int mx=0;
	for (int i=2;i<=n;i++) mx=std::max(mx,ans[i]);
	printf("%d\n",mx);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值