AtCoder Grand Contest 10 Task C Solution
闲话
思维转换难度其实应该不到紫。(虽然也不是完全独立想出来的)
题解
首先假设这棵树一定可以取完所有的石子。
根据题目的定义,在操作过的所有路径中,对于任意一个叶子 \(i\),一定有恰好 \(a_i\) 条路径以 \(i\) 为端点。对于任意一个非叶子节点 \(j\),一定有恰好 \(a_j\) 条路径穿过 \(j\)。
上述条件成立的前提是选择的所有路径的端点必须是叶子。
考虑将点条件转换为边条件。
对于任意叶子节点 \(i\),其唯一邻接边一定会被恰好 \(a_i\) 条选定路径经过。对于任意非叶子节点 \(j\),越过该节点的每条路径一定会恰好包含两条与该节点相邻的边,所以对于该节点,所有选中路径经过所有与 \(j\) 相邻的边的总次数一定为 \(2a_j\)。
根据上方提到的性质,不难使用一次 DFS 求出所有边的经过次数。具体实现上,需要随机指定一个非叶子节点作为根节点进行深搜,对于 \(n=2\) 的情况特判。
求出所有边的经过次数后,考虑如何验证合法操作方案的存在性。由上文可知,若一个非叶子节点 \(j\) 周围存在一条边的经过次数为负数或该点周围所有边的经过次数之和为奇数则操作方案不存在。否则,方案存在。
时间复杂度 \(O(n)\)。
代码
#include <cstdio>
#include <cstdlib>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
using ll = long long;
ll a[N], l[N];
int n;
vector<int> road[N];
void dfs(int x, int f)
{
// printf("%d %d\n", x, f);
if (road[x].size() == 1 and road[x][0] == f)
{
l[x] = a[x];
return;
}
for (auto &i : road[x])
{
if (i == f)
continue;
dfs(i, x);
l[x] += l[i];
}
l[x] = 2 * a[x] - l[x];
if (l[x] > a[x])
{
puts("NO");
exit(0);
}
for (auto &i : road[x])
{
if (i == f)
continue;
if (l[i] > a[x])
{
puts("NO");
exit(0);
}
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", a + i);
}
for (int i = 1, x, y; i < n; i++)
{
scanf("%d%d", &x, &y);
road[x].emplace_back(y);
road[y].emplace_back(x);
}
if (n == 2)
{
puts(a[1] == a[2] ? "YES" : "NO");
return 0;
}
for (int i = 1; i <= n; i++)
{
if (road[i].size() == 1)
continue;
dfs(i, 0);
if (l[i])
{
puts("NO");
exit(0);
}
break;
}
for (int i = 1; i <= n; i++)
{
// printf("%lld%c", l[i], " \n"[i == n]);
if (l[i] < 0)
{
puts("NO");
exit(0);
}
}
puts("YES");
}