[COCI2015-2016#1] UZASTOPNI 题解

前言

题目链接:洛谷

题意简述

一棵有根树,节点数 \(n \leq 10^5\),每个点有权值 \(v_i \leq 2000\),现在选出一些点,满足:

  1. 一个点的父亲点若未被选择则其不能被选择。
  2. 所选点的集合内不能有相同的权值。
  3. 对于每一个选择的点,其子树中所有被选择点的权值必须可以构成公差为 \(1\) 的等差数列。

求满足上述条件构成的等差数列的方案数。

题目分析

凭直觉是树形 DP。考虑记 \(f[i][l][r]\) 表示在 \(i\) 子树,\(i\) 必选,能否构成 \(l \sim r\) 的等差数列。发现有冗余状态,即由于 \(i\) 必选,一定有 \(l \leq v_i \leq r\),其他状态是不合法的。

进一步发现,对于 \(yzh \in \operatorname{son}(i)\),她对 \(i\) 的贡献要么全在 \(v_i\) 左边,要么全在右边,否则会因为已经选中了一个权值为 \(v_i\) 的点不符合要求。进一步发现,如果 \(v_{yzh} < v_i\),她只能贡献左边,反之如果 \(v_{yzh} > v_i\) 贡献右边。即 \(yzh\) 不会对 \(i\) 两边都产生贡献。

所以将状态砍半,记 \(L[i][l]\)\(R[i][r]\) 分别表示在 \(i\) 子树,\(i\) 必选,能否构成 \(l \sim v_i\) 的等差数列或 \(v_i \sim r\) 的等差数列。边界为 \(L[i][v_i] = R[i][v_i] = \text{true}\)。由于讨论过一个孩子不可能对左右多产生贡献,所以 \(L\)\(R\) 是独立的,答案就是乘法原理,\(L[1]\) 中为 \(\text{true}\) 的个数与 \(R[1]\) 中为 \(\text{true}\) 的个数之积即为答案。

说了这么多,考虑转移。考虑 \(v_{yzh} < v_i\) 的情况,反之同理。如果存在一个值 \(k\),满足在 \(yzh\) 中能够得到 \(v_{yzh} \sim k\),并且在之前的 \(i\) 中能够得到 \(k + 1 \sim v_i\),那么,就可以把 \(L[yzh]\) 或到 \(L[i]\) 上。需要注意的是,转移的顺序也要考虑。即我们要先考虑 \(v_{yzh}\) 靠近 \(v_i\) 的孩子,这样后续可能会使用到这次更新过来的信息。

时间复杂度:\(\Theta(n(V + \log n))\),如果使用 bitset 优化,则是:\(\Theta(\cfrac{nV}{w} + n \log n)\)

代码

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main() { return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;

#include <algorithm>
#include <vector>
#include <bitset>

int n, val[100010];
vector<int> edge[100010];
bitset<2010> L[100010], R[100010];

void dfs(int now) {
	L[now][val[now]] = R[now][val[now]] = 1;
	for (const auto& to: edge[now]) dfs(to);
	for (int i = 0; i < (int)edge[now].size(); ++i) {
		int to = edge[now][i];
		if (val[to] > val[now]) {
			if (((R[now] << 1) & L[to]).any()) {
				R[now] |= R[to];
			}
		}
	}
	for (int i = (int)edge[now].size() - 1; i >= 0; --i) {
		int to = edge[now][i];
		if (val[to] < val[now]) {
			if (((L[now] >> 1) & R[to]).any()) {
				L[now] |= L[to];
			}
		}
	}
}

signed main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &val[i]);
	for (int i = 1, u, v; i <= n - 1; ++i) {
		scanf("%d%d", &u, &v);
		edge[u].push_back(v);
	}
	for (int i = 1; i <= n; ++i)
		sort(edge[i].begin(), edge[i].end(), [] (const int &a, const int &b) {
			return val[a] < val[b];
		});
	dfs(1);
	printf("%llu", L[1].count() * R[1].count());
	return 0;
}
posted @ 2024-07-22 22:21  XuYueming  阅读(0)  评论(0编辑  收藏  举报