[BZOJ3697][[FJ2014集训]采药人的路径][点分治]
题目大意:
给定一棵 N≤100,000 的无根树,树边的权值为 0,1 ,求树上有多少条路径中 0,1 的数量相等且把这条路径在某一点分成两条子路径,每条子路径中 0,1 的数量也相等。
思路:
这题一眼就看出来要用点剖(其实是我在百度上搜的点剖题),然而并不会做,于是去黄学长的博客里学习了一发。
首先为了方便,我们把权值为
0
的路径权值改为
将树点剖以后,对于每个重心,我们只需要考虑重心子树中每个点到根的路径,一条符合题目要求的路径,肯定能分成两部分:
接着考虑休息站的条件,我们可以对于两条路径分开考虑,每条子路径在
这样我们枚举根节点(重心)的每个子树。用
f[i][0…1]
,
g[i][0…1]
分别表示当前子树以及前面几个子树和为
i
的路径数目,
公式中的 d 为当前子树的深度。
代码:
注意
#include <cstdio>
const int Maxn = 100010;
typedef long long ll;
inline ll Max(const ll &a, const ll &b) {
return a > b ? a : b;
}
namespace IO {
inline char get(void) {
static char buf[1000000], *p1 = buf, *p2 = buf;
if (p1 == p2) {
p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
if (p1 == p2) return EOF;
}
return *p1++;
}
inline void read(ll &x) {
x = 0; static char c;
for (; !(c >= '0' && c <= '9'); c = get());
for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get());
}
inline void write(ll x) {
if (!x) return (void)puts("0");
if (x < 0) putchar('-'), x = -x;
static short s[12], t;
while (x) s[++t] = x % 10, x /= 10;
while (t) putchar('0' + s[t--]);
putchar('\n');
}
};
ll head[Maxn], sub;
struct Edge {
ll to, nxt, v;
Edge(void) {}
Edge(const ll &to, const ll &nxt, const ll &v) : to(to), nxt(nxt), v(v) {}
} edge[Maxn << 1];
inline void add(ll a, ll b, ll v) {
edge[++sub] = Edge(b, head[a], v), head[a] = sub;
}
ll S, root, siz[Maxn], son[Maxn], n, t[Maxn << 1], f[Maxn << 1][2], g[Maxn << 1][2], mdep, dep[Maxn], len[Maxn];
bool vis[Maxn];
inline void getroot(ll u, ll fa) {
siz[u] = 1; son[u] = 0;
for (ll i = head[u], v; i; i = edge[i].nxt) {
v = edge[i].to;
if (vis[v] || v == fa) continue;
getroot(v, u);
siz[u] += siz[v];
son[u] = Max(son[u], siz[v]);
}
son[u] = Max(son[u], S - siz[u]);
if (son[u] < son[root]) root = u;
}
inline void dfs(ll u, ll fa) {
mdep = Max(dep[u], mdep);
f[len[u]][t[len[u]] > 0]++;
t[len[u]]++;
for (ll i = head[u], v; i; i = edge[i].nxt) {
v = edge[i].to;
if (vis[v] || v == fa) continue;
dep[v] = dep[u] + 1;
len[v] = len[u] + edge[i].v;
dfs(v, u);
}
t[len[u]]--;
}
ll ans;
inline void work(ll x) {
vis[x] = 1;
g[n][0] = 1;
ll mx = 0;
for (ll i = head[x], v; i; i = edge[i].nxt) {
v = edge[i].to;
if (vis[v]) continue;
mdep = 1;
dep[v] = 1;
len[v] = edge[i].v + n;
dfs(v, 0);
mx = Max(mx, mdep);
ans += (g[n][0] - 1) * f[n][0];
for (ll k = -mdep; k <= mdep; k++)
ans += f[n + k][1] * g[n - k][1] + f[n + k][1] * g[n - k][0] + f[n + k][0] * g[n - k][1];
for (ll k = -mdep; k <= mdep; k++) {
g[n + k][0] += f[n + k][0];
g[n + k][1] += f[n + k][1];
f[n + k][0] = f[n + k][1] = 0;
}
}
for (ll i = -mx; i <= mx; i++)
g[n + i][1] = g[n + i][0] = 0;
for (ll i = head[x], v; i; i = edge[i].nxt) {
v = edge[i].to;
if (vis[v]) continue;
S = siz[v], root = 0;
getroot(v, 0);
work(root);
}
}
int main(void) {
//freopen("in.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
IO::read(n);
for (ll i = 1, a, b, v; i < n; i++) {
IO::read(a), IO::read(b), IO::read(v);
if (!v) --v;
add(a, b, v), add(b, a, v);
}
S = son[0] = n;
getroot(1, 0);
work(root);
IO::write(ans);
return 0;
}
完。
By g1n0st