BZOJ3697 采药人的路径

Description

​ 给定\(n\)个点的树, 每条边的权值为0/1, 求满足路径上的0与1的个数相等.并且路径上存在一点, 它到起点与终点的路径上0与1的个数相等.求路径条数.

n <= 100000

Solution

一看到这题是统计树上路径的问题, 果断大力点分治.

考虑没有路径上存在的特殊点怎么做, 我们记边权为0的边的边权为-1, 然后只要统计树上路径长度为0的路径条数即可, 这就是裸的点分治/.

但这个特殊点的存在就使题目本身变的很毒瘤,考虑加了特殊点怎么搞.

显然在根节点到某点的路径上, 如果有到端点深度等于端点的深度的点, 那此点可以作为特殊点

在当前的过分治中心的路径中.我们可以简单分类为三种:以重心为特殊点,特殊点在起点方向,特殊点在终点方向.于是我们像前面那道题目一样,采用前缀和思想:记\(f[i][0/1]\)表示根节点已经遍历过的子树中深度为i,路上有没有深度相同的点.记\(g[i][0/1]\)表示当前处理的子树内的情况, 其余意义与f相同.

那么第一种答案就为:\(f[0][0] * g[0][0]\)

关于第二三种, 我们枚举深度:\(\sum_{i = -Dep}^{Dep} f[i][0] * g[-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1]\)

然后直接统计即可

Codes

#include<bits/stdc++.h>
using namespace std;
#define rep(i, a, b) for(int i = (a), i##_end_ = (b); i <= i##_end_; ++i)
#define drep(i, a, b) for(int i = (a), i##_end_ = (b); i >= i##_end_; --i)
#define clar(a, b) memset((a), (b), sizeof(a))
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define Debug(s) debug("The massage in line %d, Function %s: %s\n", __LINE__, __FUNCTION__, s)
typedef long long LL;
typedef long double LD;
const int BUF_SIZE = (int)1e6 + 10;
struct fastIO {
    char buf[BUF_SIZE], buf1[BUF_SIZE];
    int cur, cur1;
    FILE *in, *out;
    fastIO() {
        cur = BUF_SIZE, in = stdin, out = stdout;
        cur1 = 0;
    }
    inline char getchar() {
        if(cur == BUF_SIZE) fread(buf, BUF_SIZE, 1, in), cur = 0;
        return *(buf + (cur++));
    }
    inline void putchar(char ch) {
        *(buf1 + (cur1++)) = ch;
        if (cur1 == BUF_SIZE) fwrite(buf1, BUF_SIZE, 1, out), cur1 = 0;
    }
    inline void flush() {
        if (cur1 > 0) fwrite(buf1, cur1, 1, out);
        cur1 = 0;
    }
}IO;
#define getchar IO.getchar
#define putchar IO.putchar
int read() {
    char ch = getchar();
    int x = 0, flag = 1;
    for(;!isdigit(ch); ch = getchar()) if(ch == '-') flag *= -1;
    for(;isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    return x * flag;
}
void write(LL x) {
    if(x >= 10) write(x / 10);
    putchar(x % 10 + 48);
}

#define Maxn 100009
struct edge {
    int to, nxt, w;
}g[Maxn << 1];
int n, head[Maxn], e;
void add(int u, int v, int w) {
    g[++e] = (edge){v, head[u], w}, head[u] = e;
}
int size[Maxn], vis[Maxn];
void dfs_init(int u, int f) {
    size[u] = 1;
    for(int i = head[u]; ~i; i = g[i].nxt) {
        int v = g[i].to;
        if(v != f && !vis[v]) {
            dfs_init(v, u);
            size[u] += size[v];
        }
    }
}

int heart, BEST;
void centroid(int u, int fa, int field) {
    int remain = field - size[u];
    for(int i = head[u]; ~i; i = g[i].nxt) {
        int v = g[i].to;
        if(!vis[v] && v != fa) {
            centroid(v, u, field);
            remain = max(remain, size[v]);
        }
    }
    if(remain < BEST) BEST = remain, heart = u;
}
LL f[Maxn << 1][2], G[Maxn << 1][2], dep[Maxn << 1], vis1[Maxn << 1], Dep[Maxn << 1];
LL ans, MaxDep;
void count(int u, int fa) {
/**/MaxDep = max(MaxDep, Dep[u]);
    (++vis1[dep[u]] >= 2) ? ++G[dep[u]][1] : ++G[dep[u]][0];
    for(int i = head[u]; ~i; i = g[i].nxt) {
        int v = g[i].to;
        if(!vis[v] && v != fa) {
            Dep[v] = Dep[u] + 1;
            dep[v] = dep[u] + g[i].w;
            count(v, u);
        }
    }
/**/--vis1[dep[u]];
}
LL GMax = 0;
void calc(int u) {
    f[n][0] = 1; vis1[0] = 1;
    GMax = 0;
    for(int i = head[u]; ~i; i = g[i].nxt) {
        int v = g[i].to;
        if(!vis[v]) {
            dep[v] = g[i].w + n; MaxDep = Dep[v] = 1;
            count(v, u);
            GMax = max(GMax, MaxDep);
/**/        ans += (f[n][0] - 1) * G[n][0];
            rep(j, -MaxDep, MaxDep) 
                ans += f[n + j][0] * G[n - j][1] + f[n + j][1] * G[n - j][0] + f[n + j][1] * G[n - j][1];
            rep(j, n - MaxDep, n + MaxDep) {
                f[j][0] += G[j][0], G[j][0] = 0;
                f[j][1] += G[j][1], G[j][1] = 0;
            }
        }
    }
    f[n][0] = 0; vis1[0] = 0;
    rep(j, n - GMax, n + GMax) 
        f[j][0] = G[j][0] = f[j][1] = G[j][1] = 0;
}
void divide_conquer(int u) {
    dfs_init(u, 0), BEST = INT_MAX; 
    centroid(u, 0, size[u]);
    calc(heart), vis[heart] = 1;
    for(int i = head[heart]; ~i; i = g[i].nxt) {
        int v = g[i].to;
        if(!vis[v]) divide_conquer(v);
    }
}
int main() {
/**/clar(head, -1);
    n = read();
    rep(i, 1, n - 1) {
        int a = read(), b = read(), c = read() ? 1 : -1;
        add(a, b, c), add(b, a, c);
    }
    divide_conquer(1);
    cout << ans << endl;
#ifdef Qrsikno
    cerr << clock() * 1.0 / CLOCKS_PER_SEC << endl;
#endif
    return 0;
}

Debug

1.链式前向星忘记初始化(这强调过\(N^N\)遍辣!)

2.点分治没有初始化\[g[0][0]\] = 1,计算时也要相应减去.

3.清空数组时只要把当前用过的深度清除. 不要全清除, 不然是\[O(n^2)\]的.

转载于:https://www.cnblogs.com/qrsikno/p/9791352.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值