hdu 5390 tree (线段树套trie树)
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=5390
题意
给定一棵\(n\)个节点的带点权树,进行\(m\)次操作,每次操作属于以下两类:
- 修改一个节点的权值.
- 对给定的节点\(u\),求在\(u\)到\(1\)的路径上取节点\(v\),能得到的\(val_u⊗val_v\)的最大值.
\(1≤n,m≤10^5,0≤val_i≤10^9\)
思路
查询\(x\)到根节点的路径上的点的影响,所以修改一个点会影响它的子树的答案。考虑按\(dfs\)序来维护一棵线段树,每次将修改更新到它的\(dfs\)序区间。找异或的最大值也自然会往\(Trie\)树上靠。所以我们可以用线段树套\(trie\)树,线段树的每一个结点维护一棵\(01\)字典树,每次结点修改权值,在它的dfs序区间更新字典树即可。时间复杂度为\(32nlog_2n\)。
查询有两种方法,一种是在修改时设个懒惰标记,然后查询的时候一路推下来到叶子节点再做查询,这种方法空间开销非常大。
另一种是不做懒惰标记,可以只在线段树的这个节点的\(Trie\)树上进行删除和插入操作,然后单点询问时对线段树路径上的每个节点的\(Trie\)树都贪心地求解一次取最大值即可。
用第二种方法的话trie树的大小就要开到\(32nlog_2n\),会超内存。先附上这种方法的代码
#include<bits/stdc++.h>
using namespace std;
const int maxx = 1e5+10;
vector<int>ma[maxx];
int w[maxx];
struct Trie
{
int trie[32*20*maxx][2],tot;
int sum[32*20*maxx];
void init()
{
tot=0;
trie[0][0]=trie[0][1]=sum[0]=0;
}
void update(int rt,int x,int c)
{
for(int i=31;i>=0;i--)
{
int id=(x>>i)&1;
if(!trie[rt][id])
{
trie[rt][id]=++tot;
trie[tot][0]=trie[tot][1]=sum[tot]=0;
}
rt=trie[rt][id];
sum[rt]+=c;
}
}
int query(int rt,int x)
{
int res=0;
for(int i=31;i>=0;i--)
{
int id=(x>>i)&1;
if(trie[rt][id^1]&&sum[trie[rt][id^1]])rt=trie[rt][id^1],res+=(1<<i);
else rt=trie[rt][id];
}
return res;
}
}trie;
struct Tree
{
int t[maxx<<2],ans;
void build(int l,int r,int rt)
{
t[rt]=++trie.tot;
if(l==r)return;
int mid=(l+r)/2;
build(l,mid,rt*2);
build(mid+1,r,rt*2+1);
}
void update(int l,int r,int p,int q,int x,int c,int rt)
{
if(p<=l&&r<=q)
{
trie.update(t[rt],x,c);
return;
}
int mid=(l+r)/2;
if(p<=mid)update(l,mid,p,q,x,c,rt*2);
if(q>mid)update(mid+1,r,p,q,x,c,rt*2+1);
}
void query(int l,int r,int x,int c,int rt)
{
ans=max(ans,trie.query(t[rt],c));
if(l==r)return;
int mid=(l+r)/2;
if(x<=mid)query(l,mid,x,c,rt*2);
else query(mid+1,r,x,c,rt*2+1);
}
}tree;
int in[maxx],out[maxx],cnt;
void dfs(int u,int fa)
{
in[u]=++cnt;
for(int i=0;i<ma[u].size();i++)
{
int v=ma[u][i];
if(v==fa)continue;
dfs(v,u);
}
out[u]=cnt;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)ma[i].clear();
int x;
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
ma[x].push_back(i);
ma[i].push_back(x);
}
cnt=0;
dfs(1,0);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
trie.init();
tree.build(1,n,1);
for(int i=1;i<=n;i++)
tree.update(1,n,in[i],out[i],w[i],1,1);
int op,y;
while(m--)
{
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&x,&y);
tree.update(1,n,in[x],out[x],w[x],-1,1);
w[x]=y;
tree.update(1,n,in[x],out[x],w[x],1,1);
}
else
{
scanf("%d",&x);
tree.ans=0;
tree.query(1,n,in[x],w[x],1);
printf("%d\n",tree.ans);
}
}
}
return 0;
}
因为第二种方法不用懒惰标记,线段树上每个节点实际上都是独立的,所以我们可以考虑在线段树上离线做,对每个节点,把有关的各种删除,插入和更新答案的操作按顺序做一遍就行了。用\(vector\)按顺序保存节点的所有操作,最后遍历整颗线段树,每到一个节点就清空\(Trie\)树,将该结点的操作依次维护到一棵\(trie\)上。这样只需要维护一棵\(trie\),空间复杂度就优化到\(32n\)。
#include<bits/stdc++.h>
using namespace std;
const int maxx = 1e5+10;
vector<int>ma[maxx];
int w[maxx];
struct node{int op,x,c;};
int ans[maxx];
struct Trie
{
int trie[32*maxx][2],tot;
int sum[32*maxx];
void init()
{
tot=0;
trie[0][0]=trie[0][1]=sum[0]=0;
}
void update(int x,int c)
{
int rt=0;
for(int i=31;i>=0;i--)
{
int id=(x>>i)&1;
if(!trie[rt][id])
{
trie[rt][id]=++tot;
trie[tot][0]=trie[tot][1]=sum[tot]=0;
}
rt=trie[rt][id];
sum[rt]+=c;
}
}
int query(int x)
{
int rt=0,res=0;
for(int i=31;i>=0;i--)
{
int id=(x>>i)&1;
if(trie[rt][id^1]&&sum[trie[rt][id^1]])rt=trie[rt][id^1],res+=(1<<i);
else rt=trie[rt][id];
}
return res;
}
}trie;
struct Tree
{
vector<node>t[maxx<<2];
void build(int l,int r,int rt)
{
t[rt].clear();
if(l==r)return;
int mid=(l+r)/2;
build(l,mid,rt*2);
build(mid+1,r,rt*2+1);
}
void update(int l,int r,int p,int q,int x,int c,int rt)
{
if(p<=l&&r<=q)
{
t[rt].push_back(node{0,x,c});
return;
}
int mid=(l+r)/2;
if(p<=mid)update(l,mid,p,q,x,c,rt*2);
if(q>mid)update(mid+1,r,p,q,x,c,rt*2+1);
}
void query(int l,int r,int x,int c,int id,int rt)
{
t[rt].push_back(node{1,c,id});
if(l==r)return;
int mid=(l+r)/2;
if(x<=mid)query(l,mid,x,c,id,rt*2);
else query(mid+1,r,x,c,id,rt*2+1);
}
void getans(int l,int r,int rt)
{
trie.init();
for(int i=0;i<t[rt].size();i++)
{
int op=t[rt][i].op,x=t[rt][i].x,c=t[rt][i].c;
if(!op)trie.update(x,c);
else ans[c]=max(ans[c],trie.query(x));
}
if(l==r)return;
int mid=(l+r)/2;
getans(l,mid,rt*2);
getans(mid+1,r,rt*2+1);
}
}tree;
int in[maxx],out[maxx],cnt;
void dfs(int u,int fa)
{
in[u]=++cnt;
for(int i=0;i<ma[u].size();i++)
{
int v=ma[u][i];
if(v==fa)continue;
dfs(v,u);
}
out[u]=cnt;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)ma[i].clear();
int x;
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
ma[x].push_back(i);
ma[i].push_back(x);
}
cnt=0;
dfs(1,0);
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
trie.init();
tree.build(1,n,1);
for(int i=1;i<=n;i++)
tree.update(1,n,in[i],out[i],w[i],1,1);
int op,y,q=0;
while(m--)
{
scanf("%d",&op);
if(op==0)
{
scanf("%d%d",&x,&y);
tree.update(1,n,in[x],out[x],w[x],-1,1);
w[x]=y;
tree.update(1,n,in[x],out[x],w[x],1,1);
}
else
{
scanf("%d",&x);
ans[++q]=0;
tree.query(1,n,in[x],w[x],q,1);
}
}
tree.getans(1,n,1);
for(int i=1;i<=q;i++)
printf("%d\n",ans[i]);
}
return 0;
}