bzoj 5469
好神的一道题啊...
码量极小...
题意:求树上从根到叶节点的最长不下降子序列
怎么搞?
最显然的思想是树形dp,也是绝大多数的做法,但本蒟蒻不会...
所以我们换成玄学方法...
首先,我们仍然借助树形dp的思想,维护以某一个节点为根节点的子树信息
那么,我们不难发现,一个点如果能对其上的点产生贡献,那么这个点的点权一定大于等于那个点(废话)
然而,这就是我们接下来思想的来源
如果一个点的子树中可以产生一个大小为$|S|$的点集,那么这个点的父节点可以继承这个点集的条件是当且仅当父节点权值小于等于这个点集中每一个点的权值!
那么对于每个点,我们维护一个集合${S}$,其中$S_i$表示以这一点为根节点选取一个大小为$i$的点集中最大的最小点权!
那么不难看出,最后的答案就是这个集合$|S|$的大小。
接下来我们讨论转移:
对于每个叶节点$i$,他的集合中只会有一个元素$w_i$,即$S={{w_{i}}}$
接下来我们考虑合并
对于每个节点,现假设他所有子节点均已维护好了该集合$S$,那么如何合并给这个节点呢?
首先有一个贪心的思想,就是直接把这两个集合合并即可,因为这样自动排序后获得的就是新的集合。
但是怎么把根节点扔进去呢?
我们在这个集合里查出第一个比根节点权值小的位置,删除即可
为什么这样是对的?
首先回忆一个问题:如何求出一个序列的最长不下降子序列?
我们知道有一个$O(nlogn)$算法,那么类比这个算法,放到树上了自然也就是正确的了!
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #include <set> #include <vector> using namespace std; multiset <int> f[200005]; vector <int> v[200005]; int n; int w[200005]; void merge(int st,int ed) { if(f[st].size()<f[ed].size())swap(f[st],f[ed]); multiset <int>::iterator it; for(it=f[ed].begin();it!=f[ed].end();it++)f[st].insert(*it); } void dfs(int x) { for(int i=0;i<v[x].size();i++) { int to=v[x][i]; dfs(to); merge(x,to); } f[x].insert(w[x]); multiset <int>::iterator it; it=f[x].lower_bound(w[x]); if(it!=f[x].begin())it--,f[x].erase(it); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&w[i]); for(int i=2;i<=n;i++) { int x; scanf("%d",&x); v[x].push_back(i); } dfs(1); printf("%d\n",f[1].size()); return 0; }