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

50 篇文章 0 订阅
6 篇文章 0 订阅

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

题目大意:

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

思路:

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

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

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

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

这样我们枚举根节点(重心)的每个子树。用 f[i][01] g[i][01] 分别表示当前子树以及前面几个子树和为 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[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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值