您的位置:首页 > 其它

[BZOJ3697][[FJ2014集训]采药人的路径][点分治]

2017-02-28 11:37 323 查看

[BZOJ3697][[FJ2014集训]采药人的路径][点分治]

题目大意:

给定一棵N≤100,000的无根树,树边的权值为0,1,求树上有多少条路径中0,1的数量相等且把这条路径在某一点分成两条子路径,每条子路径中0,1的数量也相等。

思路:

这题一眼就看出来要用点剖(其实是我在百度上搜的点剖题),然而并不会做,于是去黄学长的博客里学习了一发。

首先为了方便,我们把权值为0的路径权值改为−1,这样一条路径上0,1数量相等可以转化为路径的权值和为0

将树点剖以后,对于每个重心,我们只需要考虑重心子树中每个点到根的路径,一条符合题目要求的路径,肯定能分成两部分:s→g,g→t(s,t同属于重心g的子树中),其中s→g的权值和为k,g→t的权值和为−k。(满足s→t的权值和为0)。

接着考虑休息站的条件,我们可以对于两条路径分开考虑,每条子路径在dfs的过程中记录当前的权值前缀,如果在同一条路径中,权值前缀和最终权值和出现了相同 pre[i]=sum[1,n],那么[i+1,n]这一段的权值和必定为0,即i和i+1这两条边共同连接的点就是休息站。

这样我们枚举根节点(重心)的每个子树。用f[i][0…1],g[i][0…1]分别表示当前子树以及前面几个子树和为i的路径数目,0和1用于区分路径上是否存在前缀和为i的节点。那么当前子树对于ans的贡献就是:

f[0][0]∗g[0][0]+∑i∈[−d,d]f[i][0]∗g[−i][1]+f[i][1]∗g[−i][0]+f[i][1]∗g[−i][1]

公式中的d为当前子树的深度。

代码:

注意f和g数组的大小都是2N(存在正负),不然这道题有很多数组溢出了都发现不了……

#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
[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
[0] - 1) * f
[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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  oi bzoj 点分治