洛谷题解:P6150 [USACO20FEB] Clock Tree S

题意描述

Farmer John 的牛棚有 \(N\) 间房间和 \(N-1\) 条走廊,形成一棵树,每间房间有一个时钟。Bessie 进入房间时,时钟向后拨动一个位置。求有多少个出发房间可以使所有时钟指向 \(12\)

解题思路

首先考虑到以下性质:

  • 时间从 0 到 12 ,为方便处理,对 12 取模,令 0 表示 12 时刻。

  • 对于一个父亲,其儿子的处理顺序不重要。

  • 由于在父子来回跳时,一个来回父子各加 1 ,父子之间时间差不变。

于是,设计以下做法:从深处节点开始,通过父子间的跳跃完成一个点的所有子节点时刻转为 0 的操作,而对于这个点剩下的时刻,可通过它和它的父亲节点跳跃完成变为 0 的操作。如此地向上递归。

故,令 \(D_u\) 表示点 \(u\) 此时的时刻,初值为原始时刻。

对于 \(v\) 的父节点 \(u\) 有如下更新:

\(D_u=(12-D_v+D_u) \bmod 12\)

最后来考虑根节点的情况,\(D_{root}\) 记录的是最终回到 \(root\) 点的时刻,当之为 0 时,恰好合格。但是,对于根,我们可以选择不回去,在这种情况下,\(D_{root}\) 的值为 1 。故当 \(D_{root}\) 为0或1时,\(root\) 点合格。

显然,这只是针对一个点的情况,复杂度 \(O(n)\) 。我们枚举每个点,判断是否可行,算法总复杂度为 \(O(n^{2})\)

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

const int NR = 2510;
int a[NR], d[NR];

//d[]数组即为上文的D。

struct edge
{
	int nxt, to;
}e[NR * 2];

int head[NR], cnt;

void add(int x, int y)
{
	e[++cnt].nxt = head[x];
	e[cnt].to = y;
	head[x] = cnt;
}

void dfs(int x, int fa)
{
	for (int i = head[x]; i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y == fa) continue;
		dfs(y, x);
		d[x] = (12 - d[y] + d[x]) % 12;
	}
}

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
	{
		scanf("%d", &a[i]);
	}
	for (int i = 1; i < n; i ++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	int ans = 0;
	for (int i = 1; i <= n; i ++)
	{
		for (int j = 1; j <= n; j ++) d[j] = a[j];
		dfs(i, 0);
		if (d[i] == 0 || d[i] == 1) ans ++;
		//cout << i << " " << d[i] << endl;
	}
	cout << ans << endl;
	return 0;
}

Further Thinking

在上述情况,不难发现,每次都在使用 \(D\) 数组,所以可以考虑使用换根法优化,这里不再赘述。

咱们不妨通过 \(D\) 的推导来探索另外的做法。

通过上述过程可以知道:(此处忽略对12取模)

\( D_{根} = \ 根节点的初值 \ - \ \sum D_{第2层节点} \\ D_{第2层节点} = \ \sum第2层节点的初值 \ - \ \sum D_{第3层节点} \\ D_{第3层节点} = \ \sum第3层节点的初值 \ - \ \sum D_{第4层节点} \\ \)

从而易得:

\( D_{根} = \ 根节点初值 \ - \ \sum第2层节点的初值 \ + \ \sum第3层节点的初值 \ - \ \sum第4层节点的初值 \ + \ \sum第5层节点的初值 \ - \ ... \)

所以首先对树黑白染色,再枚举每个起点,而这个起点是否可行的依据就是:

\(x\) 同色所有点的权值和减去与 \(x\) 异色所有点的权值和得到的这个差对 12 取模后等于 0 或 1 。

Another Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

const int NR = 2510;
int a[NR];
int col[NR];
int sum[2];

struct edge
{
	int nxt, to;
}e[NR * 2];

int head[NR], cnt;

void add(int x, int y)
{
	e[++cnt].nxt = head[x];
	e[cnt].to = y;
	head[x] = cnt;
}

void dfs(int x, int fa, int k)
{
	col[x] = k;
	sum[k] += a[x];
	for (int i = head[x]; i; i = e[i].nxt)
	{
		int y = e[i].to;
		if (y == fa) continue;
		dfs(y, x, k ^ 1);
	}
}

int main()
{
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
	{
		scanf("%d", &a[i]);
	}
	for (int i = 1; i < n; i ++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y);
		add(y, x);
	}
	dfs(1, 0, 0);
	int ans = 0;
	for (int x = 1; x <= n; x ++)
	{
		if (((sum[col[x]] - sum[col[x] ^ 1]) % 12 + 12) % 12 == 0 || ((sum[col[x]] - sum[col[x] ^ 1]) % 12 + 12) % 12 == 1)
		{
			ans ++;
		}
	}
	cout << ans << endl;
	return 0;
}
posted @ 2024-10-16 21:14  hsy8116  阅读(17)  评论(0)    收藏  举报