2021牛客多校 [牛客4E]Tree Xor
链接:https://ac.nowcoder.com/acm/contest/11255/E
来源:牛客网
题目大意:
给你一颗树,n个节点,每个节点的权值为wi(未知),但是给你区间[li,ri],满足li<=wi<=ri,同时给你每条边的值表示Wu^Wv,求可能的情况数.
知识储备:
1.a^b=c 可以得到 a ^c=b;
2.满足二进制后面几位分别为00,01,10,11(或者更多),如[4,7]这样的区间异或某个数之后得到的区间还是连续的
解题思路:
@ 假若w[1]固定,那么一遍dfs可以求出每个节点的值,暴力想法就是找一个区间最短的节点,依次枚举一下。
@ 让w[1]=0,求出其他点w[i],设真实的w[1]为x,那么每个节点真实的值就为w[i]^x,其中 w[i] ^x在[li,ri]之间,只需要满足x在【[li,ri]异或w[i]后的新区间内即可,那么现在就变为了:对于一个x,只要x满足n个上述条件,那么x就是合法的
@ 我们用线段树操作,把[li,ri]分成一个个连续的且满足满足二进制后面几位分别为00,01,10,11(或者更多),如[4,7]这样的区间再异或,那么会得到一个个连续的新区间,对于每个[li,ri]都会得到很多区间,那么这些区间中重叠n次的部分就为合法解。
@ 我们可以使用差分,将得到的新区间打标记,[newl,newr]可以在newl处+1,在newr处-1,求一遍前缀和,值为n的地方都是合法解。
AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int maxn=100000+10;
int n,k,head[maxn],L[maxn],R[maxn],a[maxn];
struct node{
int to,nxt,w;
}edge[maxn*2];
int cnt=0;
struct point{
int id,zhi;
}f[maxn*100];
bool cmp(point x1,point x2){return x1.id <x2.id ;}
void add(int u,int v,int w){
edge[++k].to =v;edge[k].nxt =head[u];head[u]=k;edge[k].w =w;
}
void build(int l,int r,int L,int R,int val)
{
if(l>=L&&r<=R)
{
//满足:二进制后面几位有00,01,10,11(或者更多),如[4,7],这样的区间异或某个数之后还是连续的区间
int newl=l^(val&(~(r-l)));
// r-l在二进制下 刚好为 000001111...(为1的部分是原区间连续的长度)
// val&(~(r-l)) 则把后面连续部分全部变为0,只保留前面部分
//易知:l为 100000...的形式
int newr=newl+(r-l);
//差分, [l,r]全部加1==在l处+1,在r+1处-1
f[++cnt].id =newl;f[cnt].zhi =1;
f[++cnt].id =newr+1;f[cnt].zhi =-1;
return;
}
int mid=(l+r)/2;
if(L<=mid) build(l,mid,L,R,val);
if(R>mid) build(mid+1,r,L,R,val);
}
void dfs(int u,int fa)
{
build(0,(1<<30)-1,L[u],R[u],a[u]);
for(int i=head[u];i;i=edge[i].nxt )
{
int v=edge[i].to ;
if(v!=fa)
{
a[v]=a[u]^edge[i].w;
dfs(v,u);
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&L[i],&R[i]);
int u,v,w;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
a[1]=0;
dfs(1,-1);
sort(f+1,f+1+cnt,cmp);
int sum=0,ans=0;
for(int i=1;i<=cnt;i++)
{
sum+=f[i].zhi ;
if(sum==n) ans+=f[i+1].id -f[i].id ;
}
printf("%d",ans);
return 0;
}