T1:树节点的排序

N8

  • 枚举排列即可

N15

  • 状压dp
  • dp[i][s] 表示深度为 i,排列中选了 s 里所有点进行状态转移

树为一条链:

  • 左边与右边配对即可

N5000

  • 树形dp
  • dp[i][j] 表示在以点 i 为根的子树中,选的点深度最大为 j 进行状态转移

N106

  • 之前那个树形dp可以用长链剖分优化
  • 也可以用另一种方法
  • 分别考虑每条边对答案的贡献
  • 每条边两端连接的连通块大小不妨记为 xy
  • 那么这条边对答案最多贡献 2min(x,y)
  • 即其中一个连通块的 min(x,y) 个点穿过这条边跑到另一个连通块
  • 实际上每条边是可以同时分别取到这个最大值的
  • 那么一次 dfs 过程就能统计出来,只需要额外记录子树大小
  • 时间复杂度:O(n)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<int, int>;
int main() {
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
int n;
cin >> n;
vector<vector<P>> g(n);
rep(i, n-1) {
int a, b, c;
cin >> a >> b >> c;
--a; --b;
g[a].emplace_back(b, c);
g[b].emplace_back(a, c);
}
ll ans = 0;
vector<int> t(n, 1);
auto dfs = [&](auto f, int v, int p=-1) -> void {
for (auto [u, w] : g[v]) {
if (u == p) continue;
f(f, u, v);
t[v] += t[u];
ans += 2ll*min(t[u], n-t[u])*w;
}
};
dfs(dfs, 0);
cout << ans << '\n';
return 0;
}

加强版:

T2:数列划分

n10

  • O(2n) 枚举区间间隔,判断是否满足条件

n5000a1a2an0

  • 预处理出前缀异或和,记为 b,再用数组 s 来维护数组 b 中前缀 0 的个数:即,如果 bi=0,则 ci=1,否则 ci=0,其中 sc 的前缀和
  • 于是,原问题就转化成有 n 个数,选一些数使其呈现 x 0 x 0 ... 0 x 的样子
  • 因为必选 bn,而 bn0,所以 x=bn
  • 考虑 dpdp[i] 表示枚举到第 i 个时的答案,若 bi=x,则 dp[i]=1+dp[j]×(sisj) (j<i  bj==x),否则 dp[i]=0
  • 最终答案为 dp[n]
  • 时间复杂度:O(n2)