@CSP模拟2019.10.16 - T3@ 垃圾分类
@description@
为了保护环境,p6pou建设了一个垃圾分类器。
垃圾分类器是一个树形结构,由 n 个垃圾桶和 n-1 条双向传送带组成。
垃圾处理器的编号为 1, 2, ..., n,每条传送带都可以花 1 秒钟将垃圾从一个垃圾桶输送到另一个垃圾桶。
垃圾投放点是编号为 r 的垃圾桶,垃圾总是投放在这里。
垃圾共有 n 种,编号也是 1, 2, ..., n。
编号为 i 的垃圾会被输送到编号为 i 的垃圾桶里面,垃圾总是自动沿着最短路线输送,到达编号为 i 的垃圾桶后需要 ai 秒才能被垃圾桶处理完成。
为了避免不同垃圾混在一起,p6pou将垃圾按照编号从小到大的顺序依次投入编号为 r 的垃圾桶,每投放一种垃圾之后,必须等到这种垃圾被输送到其他垃圾桶,或者被编号为 r 的垃圾桶处理完成,才能继续投放下一种垃圾。
传送带输送垃圾也一样,如果要将垃圾从编号为 x 的垃圾桶中的垃圾传输到编号为 y 的垃圾桶,如果此时编号为 y 的垃圾桶中有另一种垃圾,必须等到另一种垃圾被传输走或者被处理完成后才能传输。
现在p6pou想知道,开始投放垃圾到垃圾分类器处理完所有所有垃圾,总共需要花费多长时间。
输入格式
第一行两个整数 n, r,垃圾种类和垃圾桶数量都是 n,垃圾投放点是编号为 r 的垃圾桶。
第二行 n 个整数 a1, a2, ..., an,表示第 i 个垃圾桶处理第 i 种垃圾的时间。
接下来 n - 1 行每行两个数 x, y,表示有一条双向传送带连接编号为 x, y 的两个垃圾桶。
输出格式
输出一个整数,表示从开始投放垃圾到处理完所有垃圾的总时间。
输入输出样例1
样例输入
3 2
5 2 5
1 2
1 3
样例输出
14
样例说明
开始投放 2 秒后,1 号垃圾到达 1 号垃圾桶,2 号垃圾到达 2 号垃圾桶,3 号垃圾还没投放;
开始投放 4 秒后,2 号垃圾处理完成;
开始投放 5 秒后,3 号垃圾到达 2 号垃圾桶,此时 1 号垃圾桶仍在处理 1 号垃圾,所以 3 号垃圾无法传输, 在此等待;
开始投放 7 秒后,1 号垃圾处理完成;
开始投放 9 秒后,3 号垃圾到达 3 号垃圾桶;
开始投放 14 秒后,3 号垃圾处理完成。此时,所有垃圾都已经处理完。
输入输出样例2
样例输入
6 4
6 5 4 3 2 1
1 2
3 2
2 4
5 4
1 6
样例输出
18
样例说明
开始投放 9 秒后,1 号垃圾处理完;
开始投放 8 秒后,2 号垃圾处理完;
开始投放 14 秒后,3 号垃圾处理完;
开始投放 12 秒后,4 号垃圾处理完;
开始投放 16 秒后,5 号垃圾处理完;
开始投放 18 秒后,6 号垃圾处理完。
数据规模与约定
对于100%的数据, n <= 100000, 0 <= ai <= 10^9, 1 <= x, y <= n。
@solution@
首先转化一下问题,去除投放垃圾这一过程:
我们从 r 开始连出去 n 个点连成一条链,并把 1~n 号垃圾从前往后放置在这些点上面。即 n -> n-1 -> ... -> 1 -> r(前面的 1~n 是垃圾放置的对应结点)。
这样就只剩下垃圾在结点之间移动了。
考虑垃圾之间一般是不会互相影响的,垃圾 x 会堵塞垃圾 y,要么是因为垃圾 x 本身在处理,要么是垃圾 x 被前面某个垃圾间接堵塞了。
我们记标记 (x, t) 表示在时刻 t 之前无法通过 x,而从时刻 t 开始可以通过结点 x(通过包含到达的情况)。
注意这里的标记是一个下界,不一定时刻 t 一定就有垃圾通过结点 x。
那么标记的产生就会两种情况:某垃圾被处理了,某垃圾被间接堵塞了。
对于第 i 个垃圾,它到达的时间受它所在结点与目的地之间的路径上所有标记的影响。
它的终点为点 i,因为这些标记 (x, t) 对于 i 的影响为 t + dep[i] - dep[x]。
我们维护 max(t - dep[x]),就可以快速求出一个点到达目的地的时间。
然后考虑怎么维护标记:
先考虑间接堵塞,我们发现这个等价于所有标记上移到结点的父亲。
如果是在链上,这个操作可以用平衡树来维护(貌似也可以用线段树)。
如果是在树上,最常见的就是树链剖分转为序列问题。但是从重链顶端上移时,需要进行类区间拆分与区间合并的操作。于是就不得不用什么平衡树来做了。
貌似用 lct 要更自然一点。不过 splay 太难写了(
然后是自己被处理,会产生一个 (max(t - dep[x]) + dep[i] + a[i] +1, x) 的标记。平衡树上直接插入即可。这里的插入必须要在标记向上爬之后再插。
当然为了保证合法,我们还要加上标记 (1, 1)。
时间复杂度 O(nlog^2n)。
@accepted code@
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF = (1LL<<60);
const int MAXN = 200000;
struct node{
ull pri;
ll tg, mx, val;
int dep;
node *ch[2];
}pl[MAXN + 5], *ncnt, *NIL;
typedef pair<node*, node*> Droot;
struct treap{
treap() {
NIL = ncnt = &pl[0];
NIL->mx = -INF;
NIL->ch[0] = NIL->ch[1] = NIL;
}
ull get_rand() {
ull p = rand(), q = (p << 16) | rand();
ull u = rand(), v = (u << 16) | rand();
return (q << 32) | v;
}
node *newnode(ll x, int d) {
node *p = (++ncnt);
p->dep = d, p->mx = p->val = x;
p->pri = get_rand(), p->tg = 0;
p->ch[0] = p->ch[1] = NIL;
return p;
}
void maintain(node *x, ll k) {
if( x == NIL ) return ;
x->tg += k, x->mx += k, x->val += k, x->dep -= k;
}
void pushup(node *x) {
x->mx = max(max(x->ch[0]->mx, x->ch[1]->mx), x->val);
}
void pushdown(node *x) {
if( x->tg ) {
maintain(x->ch[0], x->tg);
maintain(x->ch[1], x->tg);
x->tg = 0;
}
}
node *merge(node *x, node *y) {
if( x == NIL ) return y;
if( y == NIL ) return x;
pushdown(x), pushdown(y);
if( x->pri < y->pri ) {
x->ch[1] = merge(x->ch[1], y);
pushup(x); return x;
}
else {
y->ch[0] = merge(x, y->ch[0]);
pushup(y); return y;
}
}
Droot split(node *x, int k) {
if( x == NIL ) return make_pair(NIL, NIL);
pushdown(x);
if( k < x->dep ) {
Droot p = split(x->ch[0], k);
x->ch[0] = p.second; pushup(x);
return make_pair(p.first, x);
}
else {
Droot p = split(x->ch[1], k);
x->ch[1] = p.first; pushup(x);
return make_pair(x, p.second);
}
}
node *insert(node *x, node *y) {
Droot p = split(x, y->dep);
return merge(p.first, merge(y, p.second));
}
}T;
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
int hvy[MAXN + 5], siz[MAXN + 5], dep[MAXN + 5], fa[MAXN + 5];
void dfs1(int x, int f) {
siz[x] = 1, dep[x] = dep[f] + 1, fa[x] = f, hvy[x] = 0;
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dfs1(p->to, x);
siz[x] += siz[p->to];
if( siz[hvy[x]] < siz[p->to] )
hvy[x] = p->to;
}
}
node *rt[MAXN + 5];
int top[MAXN + 5], tid[MAXN + 5], dfn[MAXN + 5], dcnt = 0;
void dfs2(int x, int tp) {
rt[x] = NIL;
top[x] = tp, tid[x] = (++dcnt), dfn[dcnt] = x;
if( !hvy[x] ) return ;
dfs2(hvy[x], tp);
for(edge *p=adj[x];p;p=p->nxt)
if( p->to != fa[x] && p->to != hvy[x] )
dfs2(p->to, p->to);
}
void insert(int x, ll k) {
node *p = T.newnode(k - dep[x], dep[x]);
rt[top[x]] = T.insert(rt[top[x]], p);
}
ll query(int x) {
ll ret = -INF;
while( x ) {
Droot p = T.split(rt[top[x]], dep[x]);
ret = max(ret, p.first->mx);
rt[top[x]] = T.merge(p.first, p.second);
x = fa[top[x]];
}
return ret;
}
void update(int x) {
node *y = NIL;
while( x ) {
Droot p = T.split(rt[top[x]], dep[x]);
T.maintain(p.first, 1);
rt[top[x]] = T.merge(p.first, p.second);
p = T.split(rt[top[x]], dep[top[x]] - 1);
rt[top[x]] = T.insert(p.second, y), y = p.first;
x = fa[top[x]];
}
}
int a[MAXN + 5], n, r;
int main() {
freopen("garbage.in", "r", stdin);
freopen("garbage.out", "w", stdout);
srand(0307); scanf("%d%d", &n, &r);
for(int i=1;i<=n;i++)
scanf("%d", &a[i]);
for(int i=1;i<n;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u, v);
}
addedge(r, n + 1);
for(int i=2;i<=n;i++) addedge(n + i - 1, n + i);
dfs1(2*n, 0), dfs2(2*n, 2*n), insert(r, 1);
ll ans = 0;
for(int i=1;i<=n;i++) {
ll tmp = dep[i] + query(i) + a[i];
update(i), insert(i, tmp + 1);
ans = max(ans, tmp);
}
printf("%lld\n", ans);
}
@details@
出题人在嘲讽选手是垃圾
话说树链剖分套平衡树是否超出了 CSP 的考纲范围内。
(也不一定,毕竟去年连 ddp 都考)