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)$$的.