题目大意
有一棵个
n
n
n 点的树,每条边有边权,定义
f
i
,
j
f_{i,j}
fi,j 为点
i
i
i 到点
j
j
j 的路径上所有边的边权的按位与( &
值),
g
i
,
j
g_{i,j}
gi,j 为点
i
i
i 到点
j
j
j 的路径上所有边的边权的按位或( |
值),求
∑
i
=
1
n
∑
j
=
i
+
1
n
f
i
,
j
×
g
i
,
j
\sum_{i=1}^{n} \sum_{j=i+1}^{n} f_{i,j} \times g_{i,j}
∑i=1n∑j=i+1nfi,j×gi,j 模
998244353
998244353
998244353 的值。
解题思路
首先看到按位或和按位与和这毒瘤的数据范围,就可以想到只能用 二 进 制 \color{red}二进制 二进制 来解。
类似于这道题 → \to → 奇怪的最短路
题面
给定长度为 n n n 的数列 a a a,如果 a i a n d a j ! = 0 a_i \ and \ a_j != 0 ai and aj!=0(按位与),则在 i i i, j j j 之间存在一条长度为 a i + a j a_i + a_j ai+aj 的边,求 1 1 1 至所有点的最短路。
普及一下:
按位与运算( a n d and and )
定义:参加运算的两个数据,按二进制位进行"与"运算。
运算规则:
0&0=0 ,0&1=0 ,1&0=0 ,1&1=1
总结:两位同时为 1 1 1,结果才为 1 1 1,否则结果为 0 0 0。
例如:
3
a
n
d
5
3 \ and \ 5
3 and 5 即 0000 0011 & 0000 0101 = 0000 0001
,因此
3
a
n
d
5
3 \ and \ 5
3 and 5 的值得
1
1
1。
按位或运算符( o r or or )
定义:参加运算的两个对象,按二进制位进行"或"运算。
运算规则:
0|0=0 ,0|1=1 ,1|0=1 ,1|1=1
总结:参加运算的两个对象只要有一个为 1 1 1,其值为 1 1 1。
例如:
3
o
r
5
3 \ or \ 5
3 or 5 即 0000 0011 | 0000 0101 = 0000 0111
,因此,
3
o
r
5
3 \ or \ 5
3 or 5 的值得
7
7
7 。
对于二进制,首先想到的就是 拆 位 \color{red}拆位 拆位。
类似于乘法分配律,我们可以枚举第 i i i 位和 第 j j j 位,计算有多少对 x , y x,y x,y 满足 f x , y f_{x,y} fx,y 的第 i i i 位和 f x , y f_{x,y} fx,y 的第 j j j 位都为 1 1 1,那么答案即为 x , y x,y x,y 的对数乘上 2 i + j 2^{i+j} 2i+j。
先看第 i i i 位为 1 1 1 的 f x , y f_{x,y} fx,y,因为我们希望最后按位与得到的结果第 i i i 位为 1 1 1,那么路径上的边权第 i i i 位一定都是 1 1 1。所以我们只保留边权的第 i i i 位为 1 1 1 的边,那么在每个连通块内的任意两点 < u , v > <u,v> <u,v> 满足 f u , v f_{u,v} fu,v 的第 i i i 位为 1 1 1,用并查集维护即可。
再看第 j j j 位为 1 1 1 的 g x , y g_{x,y} gx,y 的个数,显然,这很难求,因为 o r or or 运算不好处理为 1 1 1 的情况,那就需要处理为 0 0 0 的情况。
根据 容 斥 原 理 \color{red}容斥原理 容斥原理,易得 f x , y f_{x,y} fx,y 第 i i i 位和 g x , y g_{x,y} gx,y 第 j j j 位都为 1 1 1 的情况数 = = = f x , y f_{x,y} fx,y 第 i i i 位为 1 1 1 的情况数 − - − f x , y f_{x,y} fx,y 第 i i i 位为 1 1 1, g x , y g_{x,y} gx,y 第 j j j 位为 0 0 0 的情况数。
十 年 O I 一 场 空 , 不 开 l o n g l o n g 见 祖 宗 \color{red}{十年 \ OI \ 一场空,不开 \ long \ long \ 见祖宗} 十年 OI 一场空,不开 long long 见祖宗
AC CODE
快读终于缩短了,祭
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define _ (int) 5e4 + 5
#define MOD 998244353
struct Fastio
{
template <typename T>
inline Fastio operator>>(T &x)
{
x = 0;
char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return *this;
}
template <typename T>
inline Fastio &operator<<(T x)
{
if (x == 0)
{
putchar('0');
return *this;
}
if (x < 0) putchar('-'), x = -x;
static int sta[45];
int top = 0;
while (x) sta[++top] = x % 10, x /= 10;
while (top) putchar(sta[top] + '0'), --top;
return *this;
}
} oi;
int n, ans;
int u[_], v[_], w[_];
int fa[_], siz[_];
void init()
{
for (int i = 1; i <= n; i++)
{
fa[i] = i;
siz[i] = 1;
}
}
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
x = find(x), y = find(y);
if (x != y)
{
fa[y] = x;
siz[x] += siz[y];
}
}
signed main()
{
oi >> n;
for (int i = 1; i < n; ++i) oi >> u[i] >> v[i] >> w[i];
for (int i = 0; i <= 20; i++)
{
for (int j = 0; j <= 20; j++)
{
int cnt1 = 0, cnt2 = 0;
init();
for (int k = 1; k < n; k++)
if (w[k] & (1 << i)) // 第 i 位和第 j 位都为 1 的
{
merge(u[k], v[k]);
}
for (int k = 1; k <= n; k++)
if (k == find(k))
{
cnt1 = (cnt1 + (siz[k] - 1) * siz[k] / 2 % MOD) % MOD;
}
init();
for (int k = 1; k < n; k++)
if (w[k] & (1 << i))
{
if (!(w[k] & (1 << j))) // 第 i 位为 1,第 j 位为 0 的
{
merge(u[k], v[k]);
}
}
for (int k = 1; k <= n; k++)
if (k == find(k))
{
cnt2 = (cnt2 + (siz[k] - 1) * siz[k] / 2 % MOD) % MOD;
}
ans = (ans + (1ll << (i + j)) % MOD * (cnt1 - cnt2 + MOD) % MOD) % MOD;
}
}
oi << ans;
return 0;
}