计蒜客-39277 And And And(2019西安邀请赛-点分治)

题目链接 - 计蒜客 39277

题目简述:

给出一颗有n个节点的树,第2~n的父亲和边权
求树上任意两点u,v (u < v)的路径上的子集中的两点间的疑惑和为0的个数

u=1nv=1nuE(u,v)vE(u,v)n[u<v][u<v][X(u,v)=0]\sum_{u=1}^n{ \sum_{v=1}^n{ \sum_{u'\subseteq{E(u,v)}}{ \sum_{v'\subseteq{E(u,v)}}^n{ [u<v][u'<v'][X(u',v')=0] } } } }

E(u, v)是从u到v路径上的所有点,X(u, v)是u到v路径上所有的边权异或和

题目思路:

这样每次在确定根之后,可以把边权转换为点权,根节点的权值为0。转化完之后就可以用树分治解决这个问题。

每次找到一个合法的X(u,v)=0之后,以v作为根的视角去看u的子树大小作为sons[u],再以u作为根的视角去看v的子树大小作为sons[v],那么(u,v)这条合法路径对答案的贡献就是sons[u]*sons[v]

计算答案的时候需要特殊处理直接到达根节点的所有边的情况,假设u为根,v为子节点。

因为每次根不同,所以不知道以x作为树根,y作为子节点的时候,y子树的大小,所以预处理这颗树,算出每个(u,v)边对应的v子树的大小,每次计算答案的时候可以很方便的转化每个子树大小。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// 巨菜的ACMer-Happier233

#include <bits/stdc++.h>

using namespace std;

//-----
typedef double db;
typedef long long ll;
typedef unsigned int ui;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
#define fi first
#define se second
#define pw(x) (1ll << (x))
#define bt(x, k) (((x) >> k) & 1)
#define sz(x) ((int)(x).size())
#define all(x) (x).begin(),(x).end()
#define rep(i, l, r) for(int i=(l);i<(r);++i)
#define per(i, l, r) for(int i=(r)-1;i>=(l);--i)
#define mst(t, v, n) memset(t, v, sizeof(decltype(*(t))) * (n))
#define sf(x) scanf("%d", &(x))
#ifndef ACM_LOCAL
#pragma GCC optimize(2)
#define endl '\n'
#endif

struct Edge {
int to, nxt;
ll w;
};
const int N = int(1e5 + 10);
const int M = N << 1;

struct Grahp {
int head[N];
Edge eg[M];
int tot;

void init(int n) {
memset(head, -1, sizeof(int) * ++n);
}

inline void addEdge(int u, int v, ll w) {
eg[tot] = {v, head[u], w};
head[u] = tot++;
}
} gh;

bool vis[N];
// q队列, fa祖先, sz是子树大小, smx是子树最大
int q[N], fa[N], sz[N], smx[N];

int froot(int s) {
int l, r, mn = N, rt = 0;
q[l = r = 1] = s;
while (l <= r) {
int u = q[l++];
sz[u] = 1;
smx[u] = 0;
for (int i = gh.head[u]; ~i; i = gh.eg[i].nxt) {
int v = gh.eg[i].to;
if (v == fa[u] || vis[v]) continue;
fa[v] = u;
q[++r] = v;
}
}
// 反向遍历所有点算size
while (--l) {
int u = q[l];
int mx = max(smx[u], r - sz[u]);
if (mx < mn) mn = mx, rt = u;
if (l == 1) break; // 根节点没有fa
sz[fa[u]] += sz[u];
smx[fa[u]] = max(smx[fa[u]], sz[u]);
}
return rt;
}

// sons子树方向节点个数, val根到该节点异或和, gc边后继方向的节点个数
int sons[N], gc[M];
ll val[N];
ll ans = 0;
int n;

const int MOD = int(1e9 + 7);

ll nums[N];
int cnt[N];

void go(int s, int rt) {
fa[s] = rt;
val[s] = 0;
int l, r;
// 不计算s
q[l = r = 0] = s;
int m = 0;
while (l <= r) {
int u = q[l++];
nums[m++] = val[u];
for (int i = gh.head[u]; ~i; i = gh.eg[i].nxt) {
int v = gh.eg[i].to;
if (v == fa[u] || vis[v]) continue;
fa[v] = u;
q[++r] = v;
val[v] = val[u] ^ gh.eg[i].w;
// 这个点方向后面有多少点
sons[v] = gc[i];
}
}
sort(nums, nums + m);
m = unique(nums, nums + m) - nums;
mst(cnt, 0, m);
// 遍历分支
for (int j = gh.head[s]; ~j; j = gh.eg[j].nxt) {
// 分支的根
int du = gh.eg[j].to;
if (vis[du]) continue;
q[l = r = 1] = du;
while (l <= r) {
int u = q[l++];
int k = lower_bound(nums, nums + m, val[u]) - nums;
(ans += 1ll * sons[u] * cnt[k] % MOD) %= MOD;
if (val[u] == 0) {
(ans += 1ll * sons[u] * (n - gc[j]) % MOD) %= MOD;
}
for (int i = gh.head[u]; ~i; i = gh.eg[i].nxt) {
int v = gh.eg[i].to;
if (v == fa[u] || vis[v]) continue;
q[++r] = v;
}
}
// 增加这个方向的值
while (--l) {
int u = q[l];
int k = lower_bound(nums, nums + m, val[u]) - nums;
(cnt[k] += sons[u]) %= MOD;
}
}
}

void work(int u) {
// 换根
u = froot(u);
vis[u] = true;
go(u, 0);
for (int i = gh.head[u]; ~i; i = gh.eg[i].nxt) {
int v = gh.eg[i].to;
if (vis[v]) continue;
work(v);
}
}

// 预处理边后继节点个数
int pdfs(int u, int f) {
int fg_id = -1;
int s = 1;
for (int i = gh.head[u]; ~i; i = gh.eg[i].nxt) {
int v = gh.eg[i].to;
if (v == f) { // 记录父边ID
fg_id = i;
continue;
}
int c = pdfs(v, u);
gc[i] = c;
s += c;
}
// 存在父边
if (~fg_id) gc[fg_id] = n - s;
return s;
}

void solve() {
while (cin >> n) {
gh.init(n);
for (int i = 2; i <= n; i++) {
int u, v;
ll w;
u = i;
cin >> v >> w;
gh.addEdge(u, v, w);
gh.addEdge(v, u, w);
}
mst(vis, false, n + 1);
pdfs(1, 0);
ans = 0;
work(1);
cout << ans << endl;
}
}

int main() {
#ifdef ACM_LOCAL
freopen("./data/std.in", "r", stdin);
// freopen("./data/std.out", "w", stdout);
#else
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
#endif

#ifdef ACM_LOCAL
clock_t start = clock();
#endif
int t = 1;
// cin >> t;
while (t--)
solve();
#ifdef ACM_LOCAL
clock_t end = clock();
cerr << "Run Time: " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
#endif
return 0;
}