前置技能点:LCT,线段树
如果你不知道上面的东西,请先行了解
start_of_题面
end_of_题面
正式写题解之前,先吐槽一句,为什么这题在洛谷上标签会是普及-啊!!!直接写了两个小时+调了不知道多长时间。不过看讨论好像之前的标签是入门,大佬们还真是调皮(汗)。
第一眼看到这题发现是动态维护最小生成树,支持加边删边,立刻想到LCT,因为前段时间刚刚做了NOI2014魔法森林。然后想了想觉得可以 O(N) O ( N ) 扫一遍,利用一个类似ZJOI2016大森林的做法怎么搞一搞。于是就开始敲,敲到一半发现不对,有很大问题。因为这道题是动态维护最小生成树,所以不知道在每次操作的左右区间都该干什么。我们每次加边可以很轻松维护最小生成树,但是删边就没法做了,因为我们不知道删了这条边应该重新加进去哪一条边。
于是就想换方法,想了想可持久化LCT,发现我简直在做梦,然后突然想起之前听tty大神讲课的时候讲到的一个方法——时间线段树。
我们对时间建一颗线段树,然后把出现过的边按照存在的时间区间挂在线段树上,比如样例
图中的三元组 (u,v,w) ( u , v , w ) 代表一条端点为 u u 和且边权为 w w 的边。
由于样例过大,这里只展示前六天的情况
一开始的边一直存在,所以挂在区间上,中间出现的边会挂在下面的区间,有可能是整个的,也有可能被分在线段树的多个节点,比如图中的 (1,2,1) ( 1 , 2 , 1 ) 就因为出现时间是 [1,2] [ 1 , 2 ] 所以被放在一个点上,而 (2,3,8) ( 2 , 3 , 8 ) 因为出现的时间是 [2,3] [ 2 , 3 ] 所以被分在了两个点上。
这样的话,我们从这颗线段树的根走到一个叶子节点,就对应了一天内的情况,这天内所有存在的边都会在我们路上经过的线段树的节点内存着,我们只需要在这个过程中用LCT动态地维护最小生成树,然后到了线段树的叶子节点就输出答案就可以了。
出于时间和空间的考虑,我们在每一个线段树的节点上开一个 vector v e c t o r 保存边,另外为了不浪费时间,我们可以在线段树的每个节点动态维护最小生成树的同时用另一个 vector v e c t o r 记下我们做过的操作,离开这个节点的时候,我们倒序处理这些操作,是断开边的就连回去,是连边的就断开,这样回到它的父亲节点时可以直接继续访问父亲节点的另一个儿子。
粗略计算一下,一个区间在线段树上最多被分为 logN l o g N 个区间,也就是说在这个过程中每条输入的边最多被加入删除 logN l o g N 次,线段树的节点数级别是 O(N) O ( N ) ,LCT的复杂度是均摊 O(logN) O ( l o g N ) ,题目给了7s时间,应该可过。
实测在洛谷开O2的情况下12个点跑了25320ms。
start_of_code
#include <map>
#include <cmath>
#include <vector>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int rmost = 32766;
const int N = 350000 + 5000;
int n, m, l, r;
struct Edge{
int x, y, tp;
LL w;
}us[N];
struct Opt{
int fc, x, y;
}ff[N], pus;
inline LL read()
{
LL a = 0;
char ch;
LL f = 1;
while(!((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-')));
if(ch == '-')
f = -1;
else
{
a = a * 10;
a += ch - '0';
}
while((((ch = getchar()) >= '0') && (ch <= '9')) || (ch == '-'))
{
a = a * 10;
a += ch - '0';
}
return a * f;
}
namespace LCT{
int son[N][2], big[N], fa[N], rev[N];
LL val[N];
inline int which(int x)
{
return x == son[fa[x]][1];
}
inline bool isroot(int x)
{
return x != son[fa[x]][1] && x != son[fa[x]][0];
}
inline void update(int x)
{
big[x] = x;
if(son[x][1] && val[big[son[x][1]]] > val[big[x]])
big[x] = big[son[x][1]];
if(son[x][0] && val[big[son[x][0]]] > val[big[x]])
big[x] = big[son[x][0]];
}
inline void pushdown(int x)
{
if(rev[x])
{
rev[son[x][1]] ^= 1;
rev[son[x][0]] ^= 1;
swap(son[x][1], son[x][0]);
rev[x] = 0;
}
}
inline void rotate(int x)
{
int f = fa[x], g = fa[f], d = which(x);
if(!isroot(f))
son[g][which(f)] = x;
fa[x] = g;
son[f][d] = son[x][d ^ 1];
fa[son[f][d]] = f;
son[x][d ^ 1] = f;
fa[f] = x;
update(f);
update(x);
}
int stk[N], top = 0;
inline void splay(int x)
{
top = 0;
stk[++top] = x;
for(int i = x;!isroot(i);i = fa[i])
stk[++top] = fa[i];
for(int i = top;i >= 1;--i)
pushdown(stk[i]);
for(;!isroot(x);rotate(x))
if(!isroot(fa[x]))
rotate(which(x) == which(fa[x]) ? fa[x] : x);
update(x);
}
inline void access(int x)
{
int y = 0;
for(;x;y = x, x = fa[x])
splay(x), son[x][1] = y, update(x);
}
inline void makeroot(int x)
{
access(x);
splay(x);
rev[x] ^= 1;
}
inline int findroot(int x)
{
access(x);
splay(x);
while(son[x][0])
x = son[x][0];
return x;
}
inline void link(int u, int v)
{
makeroot(u);
fa[u] = v;
splay(u);
}
inline void cut(int x, int y)
{
makeroot(x);
access(y);
splay(y);
fa[x] = son[y][0] = 0;
update(y);
}
inline int query(int u, int v)
{
makeroot(u);
access(v);
splay(v);
return big[v];
}
}
using namespace LCT;
inline void debug()
{
for(int i = 1;i < 2 * n + m;++i)
{
cout << fa[i] << " " << val[i];
if(i > n)
cout << " " << us[i - n].x << " " << us[i - n].y;
cout << endl;
}
}
namespace Seg{
vector<Edge> g[N];
vector<Opt> op[N];
int lc[N], rc[N];
inline void build(int p, int l, int r)
{
g[p].clear();
lc[p] = l, rc[p] = r;
if(l == r)
return;
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
inline void add(int p, int l, int r, Edge in)
{
if(l == lc[p] && r == rc[p])
{
g[p].push_back(in);
return;
}
int mid = (lc[p] + rc[p]) >> 1;
if(r <= mid)
add(p << 1, l, r, in);
else if(l > mid)
add(p << 1 | 1, l, r, in);
else
add(p << 1, l, mid, in), add(p << 1 | 1, mid + 1, r, in);
}
inline void solve(int p, LL ans)
{
LL down = ans;
int s = g[p].size();
for(int i = 0;i < s;++i)
{
Edge o = g[p][i];
int u = o.x, v = o.y;
bool f = 1;
if(findroot(u) == findroot(v))
{
int I = query(u, v);
if(val[I] > o.w)
{
down -= val[I];
cut(us[I - n].x, I);
cut(I, us[I - n].y);
pus.fc = 1, pus.x = us[I - n].x, pus.y = I;
op[p].push_back(pus);
pus.fc = 1, pus.x = I, pus.y = us[I - n].y;
op[p].push_back(pus);
}
else
f = 0;
}
if(f)
{
link(u, o.tp);
link(o.tp, v);
pus.fc = 0, pus.x = u, pus.y = o.tp;
op[p].push_back(pus);
pus.fc = 0, pus.x = o.tp, pus.y = v;
op[p].push_back(pus);
down += o.w;
}
}
if(lc[p] != rc[p])
{
solve(p << 1, down);
solve(p << 1 | 1, down);
}
else
printf("%lld\n", down + 1);
if(p == 1)
return;
s = op[p].size();
for(int i = s - 1;i >= 0;--i)
{
Opt o = op[p][i];
if(o.fc == 1)
link(o.x, o.y);
else
cut(o.x, o.y);
}
}
}
using namespace Seg;
int main()
{
build(1, 1, rmost);
n = read();
for(int i = 1;i < n;++i)
{
us[i].x = read(), us[i].y = read(), us[i].w = read(), us[i].tp = n + i;
val[us[i].tp] = us[i].w;
add(1, 1, rmost, us[i]);
}
m = read();
for(int i = n;i < n + m;++i)
{
us[i].x = read(), us[i].y = read(), us[i].w = read(), us[i].tp = 2 * n - 1 + (i - n + 1);
l = read(), r = read();
val[us[i].tp] = us[i].w;
add(1, l, r, us[i]);
}
solve(1, 0);
return 0;
}