[BZOJ 4919][Lydsy1706月赛]大根堆
ao神啊这题,,,
很显然的思路是设计状态,然后线段树合并或者线段树启发式合并转移来优化一下,但是真的不好写……
考虑求LIS的那个二分+单调数组来做,那个东西的本质其实就是维护了一堆各种长度的断点一样的东西(考虑答案为\(n\),\(n - 1\)……的情况,在值的选取上各个情况之间会有断点)。那么我们就用一个multiset来维护断点,然后启发式合并即可。
考虑一棵子树,把根的子树进行合并,根加进来之后对断点会有何影响?那么考虑现在那个multiset里那个东西的后继(要lower_bound,因为如果有和根权值一样的点的话拐点不会发生变化。这里讨论有后继的情况),如果要取到那个点的长度的LIS的话,现在只需要小于等于根的权值的点就行了,取到这个点也不会使LIS变大(它不能和根共存),所以那个点就不是拐点了。并且无论是否出现这种情况,根那个点都会成为一个新的拐点。
然后DFS一遍就行啦。
代码:
/************************************************************** Problem: 4919 User: danihao123 Language: C++ Result: Accepted Time:992 ms Memory:25648 kb ****************************************************************/ #include <cstdio> #include <set> #include <vector> const int maxn = 200005; std::vector<int> G[maxn]; std::multiset<int> *st[maxn]; void merge(int x, int y) { if(st[x] -> size() < st[y] -> size()) std::swap(st[x], st[y]); while(st[y] -> size() > 0) { std::multiset<int>::iterator it = st[y] -> begin(); int v = *it; st[y] -> erase(it); st[x] -> insert(v); } } int d[maxn]; void dfs(int x) { for(int i = 0; i < G[x].size(); i ++) { int v = G[x][i]; dfs(v); merge(x, v); } std::multiset<int>::iterator it = st[x] -> lower_bound(d[x]); if(it != (st[x] -> end())) st[x] -> erase(it); st[x] -> insert(d[x]); } int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i ++) { int f; scanf("%d%d", &d[i], &f); G[f].push_back(i); } for(int i = 1; i <= n; i ++) { st[i] = new std::multiset<int>(); } dfs(1); printf("%d\n", st[1] -> size()); for(int i = 1; i <= n; i ++) { delete st[i]; } return 0; }
[HDU 4625]JZPTREE
第一次用到第二类斯特林数……
首先众所周知第二类斯特林数有一个很好康的式子:
\[x^k = \sum_{i = 0}^k S(k, i) x^{\underline{i}}\]
然后带进去试试?一番化简之后得到:
\[E_u = \sum_{i = 0}^k S(k, i) \sum_{v = 1}^n dist(u, v)^{\underline{i}}\]
那个下降幂很恶心……把它强行搞成组合数后发现:
\[E_u = \sum_{i = 0}^k S(k, i) i! \sum_{v = 1}^n \binom{dist(u, v)}{i}\]
然后考虑每个点预处理后面那个和式……显然可以树形DP苟。用经典的树形DP套路,设两个状态分别表示一个点的子树内部的贡献以及外面的点给他的贡献。至于转移,由于那是个组合数,所以可以考虑用Pascal定理苟……
代码:
#include <cstdio> #include <cstring> #include <cstdlib> #include <cctype> #include <cmath> #include <algorithm> #include <utility> const int maxn = 50005; const int maxk = 505; const int ha = 10007; int first[maxn]; int next[maxn << 1], to[maxn << 1]; int cnt; void add_edge(int u, int v) { cnt ++; next[cnt] = first[u]; first[u] = cnt; to[cnt] = v; } int n, k; int down[maxn][maxk]; void dfs_1(int x, int fa = 0) { down[x][0] = 1; for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != fa) { dfs_1(v, x); down[x][0] = (down[x][0] + down[v][0]) % ha; for(int j = 1; j <= k; j ++) { down[x][j] = (down[x][j] + down[v][j] + down[v][j - 1]) % ha; } } } } int up[maxn][maxk]; void dfs_2(int x, int fa = 0) { if(fa) { up[x][0] = n - down[x][0]; for(int j = 1; j <= k; j ++) { up[x][j] = (up[fa][j] + up[fa][j - 1] + down[fa][j] + down[fa][j - 1]) % ha; up[x][j] = (up[x][j] - (2 * down[x][j - 1]) % ha + ha) % ha; up[x][j] = (up[x][j] - down[x][j] + ha) % ha; if(j > 1) up[x][j] = (up[x][j] - down[x][j - 2] + ha) % ha; } } for(int i = first[x]; i; i = next[i]) { int v = to[i]; if(v != fa) { dfs_2(v, x); } } } int S[maxk][maxk]; int fac[maxn]; void process() { S[1][1] = 1; for(int i = 2; i <= k; i ++) { S[i][0] = 0; for(int j = 1; j < i; j ++) { S[i][j] = ((j * S[i - 1][j]) % ha + S[i - 1][j - 1]) % ha; } S[i][i] = 1; } fac[0] = 1; for(int i = 1; i <= n; i ++) { fac[i] = (fac[i - 1] * (i % ha)) % ha; } } int main() { int T; scanf("%d", &T); n = 50000; k = 500; process(); while(T --) { scanf("%d%d", &n, &k); cnt = 0; memset(first, 0, sizeof(first)); for(int i = 0; i < n - 1; i ++) { int u, v; scanf("%d%d", &u, &v); add_edge(u, v); add_edge(v, u); } memset(down, 0, sizeof(down)); memset(up, 0, sizeof(up)); dfs_1(1); dfs_2(1); for(int i = 1; i <= n; i ++) { int ans = 0; for(int j = 1; j <= k; j ++) { int f_v = (down[i][j] + up[i][j]) % ha; ans = (ans + (S[k][j] * ((f_v * fac[j]) % ha)) % ha) % ha; } printf("%d\n", ans); } } return 0; }