题目大意
给出一棵n个点的树(以1号点为根),定义
即计算∑ni=1∑nj=i+1bit[dep[i]−dep[lca(i,j)]]+bit[dep[j]−dep[lca(i,j)]]。
n≤105
解题思路
由于是计算二进制中1的个数,我们可以按位来求贡献。考虑在找lca时的倍增算法,即每次往上跳2k个节点,对于本题来说倍增跳的次数实际就是对答案的贡献。我们考虑设F[i][j]表示节点i处,上一步跳了
现在的问题就是统计状态F[i][j]对于答案的贡献。一个显然的结论就是当前状态最多只能向上跳2j−1个节点,也就是说与这些状态能求lca的点就是当前点向上2j−1个节点所处节点的子树减去当前点的子树(这个也很好理解,画个图看看就可以了)。那么把每个状态的答案累计起来就是答案了。
程序
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 1e5 + 5, MAXM = 20;
int N, top, F[MAXN][20], Fa[MAXN][20], Size[MAXN], Deep[MAXN], D[MAXN];
int tot, Last[MAXN], Next[MAXN * 2], Go[MAXN * 2];
LL Ans;
void Link(int u, int v) {
Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}
void Dfs(int Now, int Pre) {
Fa[Now][0] = Pre, Size[Now] = 1, Deep[Now] = Deep[Pre] + 1;
for (int p = Last[Now]; p; p = Next[p]) {
int v = Go[p];
if (v == Pre) continue;
Dfs(v, Now);
Size[Now] += Size[v];
}
}
int Son(int fa, int Now) {
for (int i = 19; i + 1; i --)
if (Deep[Fa[Now][i]] > Deep[fa]) Now = Fa[Now][i];
return Now;
}
void Get(int Now, int Pre) {
D[Deep[Now]] = Now;
for (int p = Last[Now]; p; p = Next[p]) {
int v = Go[p];
if (v == Pre) continue;
Get(v, Now);
}
F[Now][19] ++;
for (int i = 0; i < 20; i ++) {
int fa = Fa[Now][i];
if (!fa) break;
for (int j = i + 1; j < 20; j ++) {
if (!F[Now][j]) continue;
int top = (!Fa[fa][i]) ? 1 : D[Deep[Fa[fa][i]] + 1];
int Siz = Size[top] - Size[D[Deep[fa] + 1]];
Ans += 1ll * Siz * F[Now][j];
F[fa][i] += F[Now][j];
}
}
}
void Prepare() {
for (int j = 1; j < 20; j ++)
for (int i = 1; i <= N; i ++)
Fa[i][j] = Fa[Fa[i][j - 1]][j - 1];
}
int main() {
scanf("%d", &N);
for (int i = 1; i < N; i ++) {
int u, v;
scanf("%d%d", &u, &v);
Link(u, v), Link(v, u);
}
Dfs(1, 0);
Prepare();
Get(1, 0);
printf("%lld\n", Ans);
}