洛谷P7973 [KSN2021] Binary Land 题解
题目链接:P7973 [KSN2021] Binary Land
题意:
给定一张 $N$ 个点的图,每个点有权值 $A_i$ 和价值 $B_i$。
两个点 $x,y$ 之间存在一条无向边当且仅当 $A_x\text{ xor }A_y>\max(A_x,A_y)$。
你需要对于 $i=1,2,\cdots n$ 依次求出点 $i$ 所在连通块中所有点的价值和。
输入格式:
第一行输入一个正整数 $N$。
第二行输入 $N$ 个正整数 $A_i$。
第三行输入 $N$ 个正整数 $B_i$。
输出格式:
输出 $N$ 行,第 $i$ 行包含一个整数,代表点 $i$ 所在连通块中所有点的价值和。
数据范围:
对于所有数据,$1 \leq N \leq 10^5$,$1 \leq A_i \leq 2^{30}-1$,$1 \leq B_i \leq 10^9$。
根据常规套路,钦定 $A_x \ge A_y$ ,则边 $(x,y)$ 存在的充要条件为:$A_x$ 的第 「 $A_y$ 的最高位 」位为 $0$ 。
容易证明这种情况下,$A_x \operatorname{xor} A_y > A_x$ 。但是因为 $y$ 的数量很多,所以考虑用并查集维护连通性。
直接把 $x,y$ 合并并不是很方便统计,我们可以加入 $64$ 个点,表示每一个二进制位,然后通过他们连接 $x,y$ 。
时间复杂度 $\mathcal{O}(n \log^2 n)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 225))
char vis1[35],vis2[35];
int n,a[N],b[N],f[N],val[N],hib[N];
void init() { for(int i = 1; i <= n + 66; i++) f[i] = i; }
int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
void merge(int x,int y) {
x = find(x), y = find(y); if(x != y) { f[x] = y; val[y] += val[x]; }
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n; init();
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) { cin >> b[i], val[i] = b[i]; }
for(int i = 1; i <= n; i++)
{
int j = 64 - __builtin_clzll(a[i]);
vis1[j] = true; hib[i] = j;
while(j--) if(!(a[i] & (1 << (j - 1)))) vis2[j] = true;
}
for(int i = 1; i <= n; i++)
{
if(vis2[hib[i]]) merge(i, hib[i] + n);
for(int j = 64 - __builtin_clzll(a[i]); j; j--) {
if(!(a[i] & (1 << (j - 1))) && vis1[j]) merge(i, j + n);
}
}
for(int i = 1; i <= n; i++) cout << val[find(i)] << '\n';
return 0;
}