题目大意
有一个有nnn个点mmm条边的图,第iii条边有一个边权cic_ici。
给定KKK,有qqq次询问,每次给出一个xxx,如果ci⊕x<Kc_i\oplus x<Kci⊕x<K,则这条边存在,否则不存在。对于每个询问,输出互相连通的点对个数(即有多少1≤i<j≤n1\leq i<j\leq n1≤i<j≤n使得i,ji,ji,j连通)。
1≤n,m≤105,0≤ci,x,K<2301\leq n,m\leq 10^5,0\leq c_i,x,K<2^{30}1≤n,m≤105,0≤ci,x,K<230
时间限制2000ms2000ms2000ms,空间限制512MB512MB512MB。
题解
我们可以想到,比较ci⊕xc_i\oplus xci⊕x和KKK,如果ci⊕xc_i\oplus xci⊕x和KKK的前t−1t-1t−1位都是相同的:
- 当KKK的第ttt位为111时,ci⊕xc_i\oplus xci⊕x的第ttt位为000就一定可以取到,否则继续比较
- 当KKK的第ttt位为000时,ci⊕xc_i\oplus xci⊕x的第ttt位为000才能满足条件,还要继续往下比较
那我们把ci⊕Kc_i\oplus Kci⊕K存储到字典树中,判断哪些xxx可以使边iii存在。设当前放到了第ttt位,ci⊕Kc_i\oplus Kci⊕K的第ttt位为ppp:
- 如果KKK的第ttt位为111,则当xxx的前t−1t-1t−1位和ci⊕Kc_i\oplus Kci⊕K的前t−1t-1t−1位相同且x⊕cix\oplus c_ix⊕ci的第ttt位为000时(即xxx的第ttt位与cic_ici的第ttt位相同)一定可以取到,在对应子树的根节点加上这条边,然后按ppp继续往下遍历
- 如果KKK的第ttt位为000,则继续按ppp继续往下遍历
然后,我们遍历这棵字典树,来求出每个叶子节点的答案。对于每个点,先将其能取到的边用并查集维护并计算贡献,然后遍历其子树,求完子树中叶子节点的答案之后再将这个点在图上连的边删除,所以要用带删并查集。带删并查集中要用栈来维护加入了哪些边,因为不能路径压缩,所以要用dsu on tree\text{dsu on tree}dsu on tree来保证每次查找的时间复杂度是O(logn)O(\log n)O(logn)的。删边时不断删去栈顶的边并将栈顶弹出,直到这个点所有在先前加入栈中的边都被删完。
这样的话,每个叶子节点的答案都算出来了,查询就是O(1)O(1)O(1)的了。
时间复杂度为O(nlogVlogn+m)O(n\log V\log n+m)O(nlogVlogn+m),其中VVV表示ci,x,Kc_i,x,Kci,x,K的值域。
可以参考代码帮助理解。
code
#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n,m,q,K,tot=1,tp=0,fa[N+5],siz[N+5];
int wh[N+5],ch[32*N+5][2];
long long now=0,ans[32*N+5];
pair<int,int>st[N+5];
vector<int>v[32*N+5][2];
struct node{
int x,y,w;
}w[N+5];
void pt(int w,int id){
int q,vq=1;
for(int i=30;i>=0;i--){
if((K>>i)&1){
v[vq][(w>>i)&1].push_back(id);
}
q=((K^w)>>i)&1;
if(!ch[vq][q]) ch[vq][q]=++tot;
vq=ch[vq][q];
}
}
int gt(int x){
int q,vq=1;
for(int i=30;i>=0;i--){
q=(x>>i)&1;
if(!ch[vq][q]) ch[vq][q]=++tot;
vq=ch[vq][q];
}
return vq;
}
int find(int ff){
if(fa[ff]!=ff) return find(fa[ff]);
return ff;
}
long long gts(int x){
return 1ll*x*(x-1)/2;
}
void merge(int x,int y){
int v1=find(x),v2=find(y);
if(v1==v2) return;
if(siz[v1]<siz[v2]) swap(v1,v2);
now-=gts(siz[v1])+gts(siz[v2]);
fa[v2]=v1;
siz[v1]+=siz[v2];
st[++tp]={v1,v2};
now+=gts(siz[v1]);
}
void del(int x,int y){
now-=gts(siz[x]);
siz[x]-=siz[y];
fa[y]=y;
now+=gts(siz[x])+gts(siz[y]);
}
void stpop(int tmp){
while(tp>tmp){
del(st[tp].first,st[tp].second);
--tp;
}
}
void solve(int u){
ans[u]=now;
if(ch[u][0]){
int tmp=tp;
for(int p:v[u][0]){
merge(w[p].x,w[p].y);
}
solve(ch[u][0]);
stpop(tmp);
}
if(ch[u][1]){
int tmp=tp;
for(int p:v[u][1]){
merge(w[p].x,w[p].y);
}
solve(ch[u][1]);
stpop(tmp);
}
}
int main()
{
// freopen("xor.in","r",stdin);
// freopen("xor.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&q,&K);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&w[i].x,&w[i].y,&w[i].w);
pt(w[i].w,i);
}
for(int i=1;i<=n;i++){
fa[i]=i;siz[i]=1;
}
for(int i=1,x;i<=q;i++){
scanf("%d",&x);
wh[i]=gt(x);
}
solve(1);
for(int i=1;i<=q;i++){
printf("%lld\n",ans[wh[i]]);
}
return 0;
}
异或查询下的连通点对计数

文章描述了一种算法,解决了一个关于图中边的存在性查询问题,通过异或运算和字典树结构,快速找出给定K条件下,哪些边存在并计算连通点对的数量。利用并查集进行贡献维护和路径删除,实现高效查询。

被折叠的 条评论
为什么被折叠?



