Solution

好题, 又是长链剖分2333

考虑怎么统计答案, 我场上的思路是统计以一个点作为结尾的最长上升链, 但这显然是很难处理的. 正解的方法是统计以每个点作为折弯点的最长上升链. 具体的内容题解已经写得很详细了, 直接看题解吧:

线性的LIS的经典做法:从左往右扫并维护f[x]表示当前长为x的LIS的最后一个数最小是多少,易证f[x]必定递增,每次新加一个数y,则在f中二分查找最小的x使得f[x]>=y,若找到则将f[x]设为y,否则在f末尾加上y。最后f的长度即为答案。

答案一定是某个点往下的不同子树中的一个最长上升子序列和一个最长下降子序列拼在一起,考虑求出每个点往下的最长单调子序列。先只考虑最长下降子序列,则如果将其从下往上看的话即是一个LIS。对树dfs,每个点u保存一个f[u][x]表示从下往上走到u时长为x的LIS的最后一个数(深度最小)最小是多少。其中对于一个点u,其儿子为v1..vm,如果u本身不在LIS内,则显然f[u][x]=min(f[v][x]),即f[u]=min(f[v])。求出这样的f[u][x]后,将u本身考虑进LIS内只需要和线性LIS一样,在f[u]中二分并插入即可,这里二分的总复杂度由于每一个点都会二分一次,为O(nlgn)。

计算f[u]的方法是将儿子的f一一暴力合并(即取min),即先合并f[v1],f[v2],再与f[v3]合并,等等。可以发现,由于此时儿子已经更新完毕,所以f[v]均不需要保存,故可以被安全的修改,比如可以将f[v1],f[v2]合并到f[v1]中,再与f[v3]合并到f[v1]中,合并完后直接令f[u]指向f[v1],不需要新的空间和复制数组的时间。这时,一次暴力合并,如合并f[v1],f[v2]需要的操作次数为f[v1]与f[v2]中短的那个的长度。另外,短的那个数组被合并后,就再也不会被用到,也不会产生其它的时间消耗,相当于这个数组被永久删除了。在这种情况下,由于每一个点最多会往某一个f中插入一个数,共插入最多n个数,而每一次操作均会使某一个f中的一个数被永久删除,故合并的复杂度总共为O(n)。

用相同的方法可以求出每个点往下的LIS,将这个数组设为g,现在考虑如何更新答案。可以发现,只需要在合并时更新答案即可。在将u的儿子v1,v2合并前,需要求出从v1到u到v2(或倒过来,下同不表)的LIS的长度。一种情况是用到u,则在f[v1]和g[v2]中二分u即可,总时间复杂度为O(nlgn);另一种情况不用到u,则在f[v1]与g[v2]中枚举长度较小的那一个,在另一个中二分即可,这样的操作次数是min(f[v1],g[v2])次二分,如果min(f[v1],g[v2])中小的那个被删除,则这样的总时间复杂度显然为O(nlgn);而如果其均未被删除,则v1与v2合并后其f,g长度分别为len(f[v1])+1,len(g[v2])+1,则这个节点与其它节点继续合并时,至少会删除一个大小为min(f[v1]+1,g[v2]+1)的数组,故这样的总时间复杂度也为O(nlgn)。故整个算法的复杂度为O(nlgn)。

我好傻啊, 居然没用lower_bound...


#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#include <cstring>
#define vector std::vector
#define max std::max
#define min std::min namespace Zeonfai
{
inline int getInt()
{
int a = 0, sgn = 1; char c;
while(! isdigit(c = getchar())) if(c == '-') sgn *= -1;
while(isdigit(c)) a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
}
const int N = (int)2e5, INF = 2139062143;
int ans = 0;
struct tree
{
struct node
{
vector<int> suc;
int w, dep, maxDepth, hvy;
}nd[N + 1];
inline void addEdge(int u, int v) {nd[u].suc.push_back(v); nd[v].suc.push_back(u);}
void getSize(int u, int pre)
{
nd[u].maxDepth = 1; nd[u].dep = ~ pre ? nd[pre].dep + 1 : 0; nd[u].hvy = -1;
for(auto v : nd[u].suc) if(v != pre) {getSize(v, u); if(nd[v].maxDepth + 1 > nd[u].maxDepth) nd[u].hvy = v, nd[u].maxDepth = nd[v].maxDepth + 1;}
}
void work(int u, int pre, int *LIS, int *LDS)
{
if(~ nd[u].hvy) work(nd[u].hvy, u, LIS, LDS);
int L = 1, R = nd[u].maxDepth, pos;
while(L <= R)
{
int mid = L + R >> 1;
if(LIS[mid] >= nd[u].w) {pos = mid; R = mid - 1;} else L = mid + 1;
}
LIS[pos] = nd[u].w; ans = max(ans, pos);
L = 1; R = nd[u].maxDepth; pos;
while(L <= R)
{
int mid = L + R >> 1;
if(LDS[mid] <= nd[u].w) {pos = mid; R = mid - 1;} else L = mid + 1;
}
LDS[pos] = nd[u].w; ans = max(ans, pos);
for(auto v : nd[u].suc) if(v != pre && v != nd[u].hvy)
{
int *_LIS = new int[nd[v].maxDepth + 2], *_LDS = new int[nd[v].maxDepth + 2];
memset(_LIS, 127, (nd[v].maxDepth + 2) << 2); memset(_LDS, 0, (nd[v].maxDepth + 2) << 2); _LIS[0] = 0; _LDS[0] = INF;
work(v, u, _LIS, _LDS);
for(int i = 1; i <= nd[v].maxDepth + 1; ++ i)
{
int L = 0, R = nd[u].maxDepth;
while(L <= R)
{
int mid = L + R >> 1;
if(LDS[mid] > _LIS[i]) {ans = max(ans, mid + i); L = mid + 1;} else R = mid - 1;
}
}
int L = 0, R = nd[v].maxDepth + 1, pos;
while(L <= R)
{
int mid = L + R >> 1;
if(_LIS[mid] >= nd[u].w) {pos = mid; R = mid - 1;} else L = mid + 1;
}
_LIS[pos] = nd[u].w; ans = max(ans, pos);
for(int i = 1; i <= nd[v].maxDepth + 1; ++ i)
{
int L = 0, R = nd[u].maxDepth;
while(L <= R)
{
int mid = L + R >> 1;
if(LIS[mid] < _LDS[i]) {ans = max(ans, i + mid); L = mid + 1;} else R = mid - 1;
}
}
L = 0; R = nd[v].maxDepth + 1;
while(L <= R)
{
int mid = L + R >> 1;
if(_LDS[mid] <= nd[u].w) {pos = mid; R = mid - 1;} else L = mid + 1;
}
_LDS[pos] = nd[u].w; ans = max(ans, pos);
for(int i = 1; i <= nd[v].maxDepth + 1; ++ i) LIS[i] = min(LIS[i], _LIS[i]), LDS[i] = max(LDS[i], _LDS[i]);
delete[] _LIS; delete[] _LDS;
}
}
inline void work()
{
getSize(1, -1);
int *LIS = new int[nd[1].maxDepth + 1], *LDS = new int[nd[1].maxDepth + 1];
memset(LIS, 127, (nd[1].maxDepth + 1) << 2); memset(LDS, 0, (nd[1].maxDepth + 1) << 2); LIS[0] = 0; LDS[0] = INF;
work(1, -1, LIS, LDS);
}
}T;
int main()
{ #ifndef ONLINE_JUDGE freopen("lis.in", "r", stdin);
freopen("lis.out", "w", stdout); #endif using namespace Zeonfai;
int n = getInt();
for(int i = 1; i <= n; ++ i) T.nd[i].w = getInt();
for(int i = 1, u, v; i < n; ++ i) u = getInt(), v = getInt(), T.addEdge(u, v);
T.work();
printf("%d\n", ans);
}

2016北京集训测试赛(十七)Problem B: 银河战舰的更多相关文章

  1. 2016北京集训测试赛(十七)Problem C: 数组

    Solution 线段树好题. 我们考虑用last[i]表示\(i\)这个位置的颜色的上一个出现位置. 考虑以一个位置\(R\)为右端点的区间最远能向左延伸到什么位置: \(L = \max_{i \ ...

  2. 2016北京集训测试赛(十七)Problem A: crash的游戏

    Solution 相当于要你计算这样一个式子: \[ \sum_{x = 0}^m \left( \begin{array}{} m \\ x \end{array} \right) \left( \ ...

  3. 2016北京集训测试赛(十六)Problem C: ball

    Solution 这是一道好题. 考虑球体的体积是怎么计算的: 我们令\(f_k(r)\)表示\(x\)维单位球的体积, 则 \[ f_k(1) = \int_{-1}^1 f_{k - 1}(\sq ...

  4. 2016北京集训测试赛(十六)Problem B: river

    Solution 这题实际上并不是构造题, 而是一道网络流. 我们考虑题目要求的一条路径应该是什么样子的: 它是一个环, 并且满足每个点有且仅有一条出边, 一条入边, 同时这两条边的权值还必须不一样. ...

  5. 2016北京集训测试赛(十六)Problem A: 任务安排

    Solution 这道题告诉我们, 不能看着数据范围来推测正解的时间复杂度. 事实证明, 只要常数足够小, \(5 \times 10^6\)也是可以跑\(O(n \log n)\)算法的!!! 这道 ...

  6. BZOJ 4543 2016北京集训测试赛(二)Problem B: thr 既 长链剖分学习笔记

    Solution 这题的解法很妙啊... 考虑这三个点可能的形态: 令它们的重心为距离到这三个点都相同的节点, 则其中两个点分别在重心的两棵子树中, 且到重心的距离相等; 第三个点可能在重心的一棵不同 ...

  7. 2016北京集训测试赛(十四)Problem B: 股神小D

    Solution 正解是一个\(\log\)的link-cut tree. 将一条边拆成两个事件, 按照事件排序, link-cut tree维护联通块大小即可. link-cut tree维护子树大 ...

  8. 2016北京集训测试赛(十四)Problem A: 股神小L

    Solution 考虑怎么卖最赚钱: 肯定是只卖不买啊(笑) 虽然说上面的想法很扯淡, 但它确实能给我们提供一种思路, 我们能不买就不买; 要买的时候就买最便宜的. 我们用一个优先队列来维护股票的价格 ...

  9. 2016北京集训测试赛(十三) Problem B: 网络战争

    Solution KD tree + 最小割树

随机推荐

  1. POJ 3041 Asteroids (二分图最小点覆盖集)

    Asteroids Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 24789   Accepted: 13439 Descr ...

  2. 倍增 - 强制在线的LCA

    LCA 描述 给一棵有根树,以及一些询问,每次询问树上的 2 个节点 A.B,求它们的最近公共祖先. !强制在线! 输入 第一行一个整数 N. 接下来 N 个数,第 i 个数 F i 表示 i 的父亲 ...

  3. 极简Node教程-七天从小白变大神(二:中间件是核心)

    当我们只引入express时,前述的那些功能都是没有启用的.那么,如何将这些功能添加进来呢?express通过其中间件机制实现了这些功能的管理.每一个中间件对应一个功能,而中间件可以是第三方库,也可以 ...

  4. Leetcode 561.数组拆分I

    数组拆分 I 给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总 ...

  5. redis linux 集群

    redis集群:官方教程 步骤: 1.安装redis 2.修改配置文件redis.conf(集群所需基础配置) port 7000 cluster-enabled yes cluster-config ...

  6. unity中的main方法

    由于方法命名的原因,无意之间把一个方法命名为了Main,然后把这个方放到了Start方法中去执行,结果运行后发现这个方法竟然执行了两次 情况如下图: -------------- 检查代码,发现脚本并 ...

  7. java替换富文本标签等

    (转自:http://www.cnblogs.com/1246447850qqcom/p/5439366.html) package q;import java.util.regex.Matcher; ...

  8. 用Vundle管理Vim插件

    作为程序员,一个好用的Vim,是极其重要的,而插件能够使原本功能羸弱的Vim变得像其他功能强大的IDE一样好用.然而下载.配置插件的过程比较繁琐,大家往往需要自己进行下载/配置等操作,如果还涉及到更新 ...

  9. 发行说明 - Kafka - 版本1.0.0

    发行说明 - Kafka - 版本1.0.0 以下是Kafka 1.0.0发行版中解决的JIRA问题的摘要.有关该版本的完整文档,入门指南以及有关该项目的信息,请参阅Kafka项目网站. 有关升级的注 ...

  10. 通过Url网络编程实现下载

    import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputS ...