Solution

感觉很久没有自己调出来一道 \(DP\) 了。

我们分类 \(DP\)。令 \(f[i][0]\) 为以这个点为根的子树内部起点为 \(i\) 的类似 \(00011111\)\(00000000\) 串的个数, \(f[i][1]\) 为以这个点为根的子树内部起点为 \(i\) 的类似 \(11111111\) 串的个数。

首先我们讨论 \(x\)\(y\),其中 \(y\)\(x\) 的爸爸。

考虑计算 \(f\) 数组。易得无论 \(w(x,y)\) 是什么值,首先爸爸的值是儿子的相应边权的值加一。考虑 \(w(x,y)==0\) 的情况,还要加上 \(f[x][1]\)。(注意还要加上连接 \(x\)\(y\) 的边)

好了我们令 \(g[i][0]\) 为以这个点为根起点为 \(i\) 的类似 \(00011111\)\(00000000\) 串的个数, \(g[i][1]\) 为以这个点为根,起点为 \(i\) 的类似 \(11111111\) 串的个数,\(g\) 的初始值赋为 \(f\)。(代码实现里我是直接用 \(f\),但是推导过程是有区别的)

好了我们放图(画质感人):

分类讨论:

  • \(w(x,y)==1\):儿子不可能继承父亲的 \(0\) 情况,所以直接用爸爸减儿子得到其他儿子的值再把儿子加回去。\(ans=g[y][1]-f[x][1]+f[x][1]+f[x][0]\)
  • \(w(x,y)==0\):这里有个坑。父亲在算 \(x\) 的时候,不仅会加 \(f[x][0]\),还会加 \(f[x][1]\),方法同上,最后还要记得加上父亲的全 \(1\) 串个数。

Code

#include <cstdio>
typedef long long ll;

const int N = 2e5 + 5;

int n, head[N], nxt[N << 1], to[N << 1], w[N << 1], cnt;
ll f[N][2], ans;

int read() {
	int x = 0, f = 1; char s;
	while((s = getchar()) > '9' || s < '0') if(s == '-') f = -1;
	while(s <= '9' && s >= '0') x = (x << 1) + (x << 3) + (s ^ 48), s = getchar();
	return x * f;
}

void addEdge(const int u, const int v, const int val) {
	nxt[++ cnt] = head[u], to[cnt] = v, head[u] = cnt, w[cnt] = val;
}

void dfs1(const int u, const int fa) {
	for(int i = head[u]; i; i = nxt[i]) {
		int v = to[i];
		if(v == fa) continue;
		dfs1(v, u);
		f[u][w[i]] += f[v][w[i]] + 1;
		if(! w[i]) f[u][0] += f[v][1];
	}
}

void dfs2(const int u, const int fa) {
	for(int i = head[u]; i; i = nxt[i]) {
		int v = to[i];
		if(v == fa) continue;
		if(w[i] == 1) f[v][1] = f[u][1];
		if(! w[i]) f[v][0] = f[u][0] - f[v][1] + f[u][1];
		dfs2(v, u);
	}
}

int main() {
	int u, v, w;
	n = read();
	for(int i = 1; i < n; ++ i) {
		u = read(), v = read(), w = read();
		addEdge(u, v, w); addEdge(v, u, w);
	}
	dfs1(1, 0); dfs2(1, 0);
	for(int i = 1; i <= n; ++ i) ans += f[i][0] + f[i][1];
	printf("%lld\n", ans);
    return 0;
}
posted on 2020-06-05 11:38  Oxide  阅读(131)  评论(1编辑  收藏  举报