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

posted @ 2018-10-15 15:41  Qrsikno  阅读(180)  评论(0编辑  收藏  举报