前置技能点:LCT,线段树,DFS序
如果你不知道上面的东西,请先行了解
start_of_题面
【问题描述】
黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了
新型的重组病毒。这种病毒的繁殖和变异能力极强。为了阻止这种病毒传播,某
安全机构策划了一次实验,来研究这种病毒。
实验在一个封闭的局域网内进行。局域网内有n台计算机,编号为
1
~
n
1~n
1~n。一
些计算机之间通过网线直接相连,形成树形的结构。局域网中有一台特殊的计算
机,称之为核心计算机。根据一些初步的研究,研究员们拟定了一个一共m步的
实验。实验开始之前,核心计算机的编号为 1,每台计算机中都有病毒的一个变
种,而且每台计算机中的变种都不相同。实验中的每一步会是下面中的一种操作:
1、RELEASE x
在编号为x的计算机中植入病毒的一个新变种。这个变种在植入之前不存
在于局域网中。
2、RECENTER x
将核心计算机改为编号为x的计算机。但是这个操作会导致原来核心计算
机中的病毒产生新变种,并感染过来。换言之,假设操作前的核心计算
机编号为y,相当于在操作后附加了一次 RELEASE y 的操作。
根据研究的结论,在植入一个新变种时,病毒会在局域网中搜索核心计算机
的位置,并沿着网络中最短的路径感染过去。
而第一轮实验揭露了一个惊人的真相:病毒的不同变种是互斥的。新变种在
感染一台已经被旧变种感染的电脑时,会把旧变种完全销毁之后再感染。但研究
员发现了实现过程中的漏洞。如果新变种在感染过程中尚未销毁过这类旧变种,
需要先花费 1 单位时间分析旧变种,才能销毁。如果之前销毁过这类旧变种,就
可以认为销毁不花费时间。病毒在两台计算机之间的传播亦可认为不花费时间。
研究员对整个感染过程的耗时特别感兴趣,因为这是消灭病毒的最好时机。
于是在m步实验之中,研究员有时还会做出如下的询问:
3、REQUEST x
询问如果在编号为x的计算机的关键集合中的计算机中植入一个新变种,
平均感染时间为多长。编号为y的计算机在编号为x的计算机的关键集合
中,当且仅当从y沿网络中的最短路径感染到核心计算机必须经过x。由
于有 RECENTER 操作的存在,这个集合并不一定是始终不变的。
至此,安全机构认为已经不需要实际的实验了,于是他们拜托你编写一个程
序,模拟实验的结果,并回答所有的询问。
【输入格式】
输入的第一行包含两个整数
n
n
n和
m
m
m,分别代表局域网中计算机的数量,以及
操作和询问的总数。
接下来
n
−
1
n − 1
n−1行,每行包含两个整数
x
x
x和
y
y
y,表示局域网中编号为
x
x
x和
y
y
y的计算
机之间有网线直接相连。
接下来
m
m
m行,每行包含一个操作或者询问,格式如问题描述中所述。
【输出格式】
对于每个询问,输出一个实数,代表平均感染时间。输出与答案的绝对误差
不超过
1
0
−
6
10^{-6}
10−6 时才会被视为正确。
【样例输入】
8 6
1 2
1 3
2 8
3 4
3 5
3 6
4 7
REQUEST 7
RELEASE 3
REQUEST 3
RECENTER 5
RELEASE 2
REQUEST 1
【样例输出】
4.0000000000
2.0000000000
1.3333333333
【样例解释】
样例中的局域网如下图所示:
下面对每个操作进行解释。解释中直接用x代表编号为x的计算机。
- 7 的关键集合中只包含 7,而从 7 到 1 的最短路径上会遇到 4 种不同的
变种(包括 7 本身的变种),故答案为 4。 - 在 3 中植入了新变种,感染了 3 和 1。(感染时间为 2)
- 3 的关键集合中包含 3、4、5、6 和 7,其感染时间分别为 1、2、2、2
和 3,平均值为 2。 - 核心计算机改为 5,同时新变种感染了 1、3 和 5。(感染时间为 2)
- 在 2 中植入了新变种,感染了 2、1、3 和 5。(感染时间为 2)
- 1 的关键集合中包含 1、2 和 8,其感染时间分别为 1、1、2,平均值为
4/3。
【数据规模和约定】
end_of_题面
昨天刷了一天LCT今天就来了道LCT,真不错
我觉得我已经LCT中毒了,一看这道题的操作就想LCT,发现直接可以做,每一个点的值就是这个点到根的虚边数量,RELEASE操作可以直接变成LCT的access操作,RECENTER就可以直接用makeroot代替。然后问题变成了查询,我们知道LCT维护链上的值非常简单,但是维护子树的值就有些问题了,~~而我又不会写TopTree,~~我们发现有很多部分的点没有换根操作,那么我们可以直接用一个树链剖分+线段树维护子树信息,我们只需要在access的过程中记录下需要修改的子树,然后在线段树上修改就可以了。
然后对于需要换根的点,我们可以考虑我们换的根和原来的根的关系,我记得还有另外一道LCT的题目用到了这个性质,有两种情况
这里我们用 r o o t root root表示当前的根, x x x表示询问的点, R R R表示我们树链剖分的根。
第一种情况, L C A ( x , r o o t ) = x LCA(x, root) = x LCA(x,root)=x,如图
这种情况,我们询问 x x x需要的信息是在 x x x的除了 v v v的其它子树和不在 x x x子树上的点,我们发现可以直接转化为DFS序上除了子树 v v v的部分的值,而子树 v v v在DFS序上会是连续的一段,所以我们查询的区间就会使分开的两段。
第二种情况,
L
C
A
(
x
,
r
o
o
t
)
≠
x
LCA(x, root) \neq x
LCA(x,root)̸=x,如图
这个时候我们发现此时 x x x的答案与我们以 R R R为根树链剖分的 x x x的子树的贡献完全一样,我们直接在原来的DFS序上查询这个区间就可以了。
说起来还挺简单,但是写起来麻烦的一批,我一整场模拟赛就做了这一道题…结果发现隔壁的sekong_qi只用了两个小时就A了而且还顺便打了第三题的暴力,我因为答案炸int爆掉5分…
%%%sekong_qi
start_of_code
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
const int N = 300000 + 3000;
int n, m, root;
char str[10];
namespace file{
inline void open()
{
freopen("recompile.in", "r", stdin);
freopen("recompile.out", "w", stdout);
}
inline void close()
{
fclose(stdin);
fclose(stdout);
}
}
namespace input{
inline int read()
{
int a = 0;
int f = 1;
char ch;
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 Edge{
int F[N], v[N << 1], nex[N << 1], EID = 1;
inline void add(int f, int t)
{
nex[EID] = F[f];
v[EID] = t;
F[f] = EID++;
}
}
namespace Treec{
int depth[N], dfn[N], l[N], r[N], sonn[N], siz[N];
int DFN = 0, w[N], DATA[N];
inline void prepare()
{
depth[1] = 1;
root = 1;
}
inline void dfs(int p, int top, int fa)
{
l[p] = ++DFN;
DATA[DFN] = w[p];
if(sonn[p])
dfs(sonn[p], top, p);
for(int i = Edge::F[p];i;i = Edge::nex[i])
{
int t = Edge::v[i];
if(t == fa || t == sonn[p])
continue;
dfs(t, t, p);
}
r[p] = DFN;
}
}
namespace Seg{
LL sum[N], lc[N], rc[N], tag[N];
inline void pushdown(int p, int l, int r)
{
if(tag[p])
{
int mid = (l + r) >> 1;
tag[p << 1] += tag[p];
tag[p << 1 | 1] += tag[p];
sum[p << 1] += 1ll * tag[p] * (mid - l + 1);
sum[p << 1 | 1] += 1ll * tag[p] * (r - mid);
tag[p] = 0;
}
}
inline void update(int p)
{
sum[p] = sum[p << 1] + sum[p << 1 | 1];
}
inline void build(int p, int l, int r)
{
lc[p] = l, rc[p] = r;
tag[p] = 0;
if(l == r)
{
sum[p] = Treec::DATA[l];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
update(p);
}
inline void change(int p, int l, int r, LL x)
{
if(l == lc[p] && r == rc[p])
{
sum[p] += x * (r - l + 1) * 1ll;
tag[p] += x;
return;
}
pushdown(p, lc[p], rc[p]);
int mid = (lc[p] + rc[p]) >> 1;
if(r <= mid)
change(p << 1, l, r, x);
else if(l > mid)
change(p << 1 | 1, l, r, x);
else
change(p << 1, l, mid, x), change(p << 1 | 1, mid + 1, r, x);
update(p);
}
inline LL query(int p, int l, int r)
{
if(l == lc[p] && r == rc[p])
return sum[p];
pushdown(p, lc[p], rc[p]);
int mid = (lc[p] + rc[p]) >> 1;
if(r <= mid)
return query(p << 1, l, r);
else if(l > mid)
return query(p << 1 | 1, l, r);
else
return query(p << 1, l, mid) + query(p << 1 | 1, mid + 1, r);
}
}
namespace LCA{
int f[20][N];
inline void dfs(int p, int fa)
{
Treec::siz[p] = 1;
Treec::sonn[p] = 0;
Treec::w[p] = Treec::depth[p];
for(int i = Edge::F[p];i;i = Edge::nex[i])
{
int t = Edge::v[i];
if(t == fa)
continue;
f[0][t] = p;
Treec::depth[t] = Treec::depth[p] + 1;
dfs(t, p);
if(Treec::siz[t] > Treec::siz[Treec::sonn[p]])
Treec::sonn[p] = t;
Treec::siz[p] += Treec::siz[t];
}
}
inline void solve()
{
for(int j = 1;j <= 19;++j)
for(int i = 1;i <= n;++i)
f[j][i] = f[j - 1][f[j - 1][i]];
}
inline int lca(int x, int y)
{
if(Treec::depth[x] < Treec::depth[y])
swap(x, y);
for(int i = 19;i >= 0;--i)
if(Treec::depth[f[i][x]] >= Treec::depth[y])
x = f[i][x];
if(x == y)
return x;
for(int i = 19;i >= 0;--i)
if(f[i][x] != f[i][y])
{
x = f[i][x];
y = f[i][y];
}
return f[0][x];
}
inline int Get(int x, int y)
{
for(int i = 19;i >= 0;--i)
if(Treec::depth[f[i][x]] > Treec::depth[y])
x = f[i][x];
return x;
}
}
inline void Modi(int p, LL w)
{
int lc = LCA::lca(p, root);
if(p == root)
Seg::change(1, 1, n, w);
else if(lc == p)
{
int v = LCA::Get(root, p);
if(Treec::l[v] - 1 >= 1)
Seg::change(1, 1, Treec::l[v] - 1, w);
if(Treec::r[v] + 1 <= n)
Seg::change(1, Treec::r[v] + 1, n, w);
}
else
Seg::change(1, Treec::l[p], Treec::r[p], w);
}
namespace LCT{
vector<int> chgminus, chgplus;
int son[N][2], fa[N], rev[N], sum[N], L[N], R[N];
inline int which(int x)
{
return x == son[fa[x]][1];
}
inline bool isroot(int x)
{
return x != son[fa[x]][0] && x != son[fa[x]][1];
}
inline void pushdown(int x)
{
if(rev[x])
{
if(son[x][0])
{
swap(son[son[x][0]][0], son[son[x][0]][1]);
swap(L[son[x][0]], R[son[x][0]]);
rev[son[x][0]] ^= 1;
}
if(son[x][1])
{
swap(son[son[x][1]][0], son[son[x][1]][1]);
swap(L[son[x][1]], R[son[x][1]]);
rev[son[x][1]] ^= 1;
}
rev[x] = 0;
}
}
inline void update(int x)
{
if(son[x][0] == 0)
L[x] = x;
else
L[x] = L[son[x][0]];
if(son[x][1] == 0)
R[x] = x;
else
R[x] = R[son[x][1]];
}
inline void rotate(int x)
{
int f = fa[x], g = fa[f], d = which(x);
if(!isroot(f))
son[g][which(f)] = x;
son[f][d] = son[x][d ^ 1];
fa[son[f][d]] = f;
son[x][d ^ 1] = f;
fa[f] = x;
fa[x] = g;
update(f);
update(x);
}
int stk[N], top = 0;
inline void splay(int x)
{
top = 0;
stk[++top] = x;
int fc;
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);
}
inline void access(int x)
{
chgminus.clear();
chgplus.clear();
int y = 0;
for(;x;y = x, x = fa[x])
{
splay(x);
if(son[x][1])
chgplus.push_back(L[son[x][1]]);
son[x][1] = y;
if(y)
chgminus.push_back(L[y]);
update(x);
}
}
inline void makeroot(int x)
{
access(x);
splay(x);
swap(son[x][0], son[x][1]);
swap(L[x], R[x]);
rev[x] ^= 1;
}
inline void prepare()
{
for(int i = 1;i <= n;++i)
fa[i] = LCA::f[0][i], L[i] = R[i] = i;
}
}
namespace SOLVE{
inline void request(int p)
{
int lc = LCA::lca(p, root);
if(p == root)
{
LL ans = Seg::query(1, 1, n);
printf("%.9f\n", (double)(ans) / n);
}
else if(lc == p)
{
int v = LCA::Get(root, p);
LL ansl = 0, ansr = 0;
if(Treec::l[v] - 1 >= 1)
ansl = Seg::query(1, 1, Treec::l[v] - 1);
if(Treec::r[v] + 1 <= n)
ansr = Seg::query(1, Treec::r[v] + 1, n);
printf("%.9f\n", (double)(ansl + ansr) / (n - Treec::siz[v]));
}
else
{
LL ans = Seg::query(1, Treec::l[p], Treec::r[p]);
printf("%.9f\n", (double)(ans) / Treec::siz[p]);
}
}
inline void release(int p)
{
LCT::access(p);
int s = LCT::chgplus.size();
for(int i = 0;i < s;++i)
Modi(LCT::chgplus[i], 1);
s = LCT::chgminus.size();
for(int i = 0;i < s;++i)
Modi(LCT::chgminus[i], -1);
}
inline void recenter(int p)
{
LCT::makeroot(p);
int s = LCT::chgplus.size();
for(int i = 0;i < s;++i)
Modi(LCT::chgplus[i], 1);
s = LCT::chgminus.size();
for(int i = 0;i < s;++i)
Modi(LCT::chgminus[i], -1);
root = p;
}
}
int main()
{
file::open();
n = input::read(), m = input::read();
for(int i = 1;i < n;++i)
{
int x = input::read(), y = input::read();
Edge::add(x, y);
Edge::add(y, x);
}
Treec::prepare();
LCA::dfs(1, -1);
LCA::solve();
Treec::dfs(1, 1, -1);
Seg::build(1, 1, n);
LCT::prepare();
for(int i = 1;i <= m;++i)
{
int x;
scanf("%s%d", str, &x);
if(str[2] == 'Q')
SOLVE::request(x);
if(str[2] == 'L')
SOLVE::release(x);
if(str[2] == 'C')
SOLVE::recenter(x);
}
file::close();
return 0;
}