3779: 重组病毒
Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 642 Solved: 241
[Submit][Status][Discuss]
Description
黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒。这种病毒的繁殖和变异能力极强。为了阻止这种病毒传播,某安全机构策划了一次实验,来研究这种病毒。
实验在一个封闭的局域网内进行。局域网内有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台计算机,编号为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操作的存在,这个集合并不一定是始终不变的。
至此,安全机构认为已经不需要实际的实验了,于是他们拜托你编写一个程序,模拟实验的结果,并回答所有的询问。
Input
输入的第一行包含两个整数n和m,分别代表局域网中计算机的数量,以及操作和询问的总数。
接下来n-1行,每行包含两个整数x和y,表示局域网中编号为x和y的计算机之间有网线直接相连。
接下来m行,每行包含一个操作或者询问,格式如问题描述中所述。
接下来n-1行,每行包含两个整数x和y,表示局域网中编号为x和y的计算机之间有网线直接相连。
接下来m行,每行包含一个操作或者询问,格式如问题描述中所述。
Output
对于每个询问,输出一个实数,代表平均感染时间。输出与答案的绝对误差不超过 10^(-6)时才会被视为正确。
Sample Input
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
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
Sample Output
4.0000000000
2.0000000000
1.3333333333
2.0000000000
1.3333333333
HINT
N < = 1 00 000 M < = 1 00 000
分析:真是一道神题......
先不考虑换根操作.每次RELEASE操作都会使得根节点与当前操作点的颜色变成相同的一段. 病毒经过这一段不需要花费时间. 如果把每条边赋上边权,两个端点的颜色相同则为0,否则为1,那么感染一个点所需的时间就是它到根节点经过的1的数量.
RELEASE操作实际上就是把一个点到根的路径上的所有边都变成了0.由此可以想到LCT的Access操作. 0代表LCT的实边,1代表LCT的虚边.问题就被转化成了从x点出发到根节点会经过多少条虚边.
这个可以在Access操作的时候顺便维护. 找到每条实链上最靠近根的那个节点,然后把它到父亲的虚边变成实边,在splay里面这就是最靠左的一个节点,原来的实边就要断成虚边.找到对应的点,对子树进行操作,需要用到线段树+dfs序. 例如:x是t的父亲,t,x要连一条实边. 那么t所在实链最靠左的一个点的子树内的所有点-1(少走一条虚边),x原来的 实儿子 所在的实链 的最靠左 的一个点的子树内 的所有点+1(多走一条虚边).
查询就是对应的子树和/子树大小.
有换根操作该怎么弄呢?类比bzoj3083,换根操作只会对子树的构成有影响,分类讨论一下位置即可.
一道挺好的题,让我对LCT和splay的理解更深了. 难点在于如何和LCT联系起来. 所以怎么样才能看出来它与LCT有关呢? 每次选一个点,对它到根的路径进行操作(覆盖).
下面这份代码在洛谷上能过,bzoj上被卡常了,只有树状数组才能过.
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 200010; ll n,m,head[maxn],to[maxn],nextt[maxn],tot = 1,sta[maxn],root,son[maxn][2],fa[maxn]; ll fa2[maxn][20],dep[maxn],pre[maxn],endd[maxn],dfs_clock,rev[maxn]; ll sum[maxn << 2],tag[maxn << 2],L[maxn << 2],R[maxn << 2],id[maxn]; void add(ll x,ll y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void pushup(ll o) { sum[o] = sum[o * 2] + sum[o * 2 + 1]; } void pushdown(ll o) { if (tag[o]) { tag[o * 2] += tag[o]; tag[o * 2 + 1] += tag[o]; sum[o * 2] += tag[o] * (R[o * 2] - L[o * 2] + 1); sum[o * 2 + 1] += tag[o] * (R[o * 2 + 1] - L[o * 2 + 1] + 1); tag[o] = 0; } } void build(ll o,ll l,ll r) { L[o] = l,R[o] = r; if (l == r) { sum[o] = dep[id[l]]; return; } ll mid = (l + r) >> 1; build(o * 2,l,mid); build(o * 2 + 1,mid + 1,r); pushup(o); } void update(ll o,ll l,ll r,ll x,ll y,ll v) { if (x <= l && r <= y) { tag[o] += v; sum[o] += (r - l + 1) * v; return; } pushdown(o); ll mid = (l + r) >> 1; if (x <= mid) update(o * 2,l,mid,x,y,v); if (y > mid) update(o * 2 + 1,mid + 1,r,x,y,v); pushup(o); } void dfs(ll u,ll faa) { fa2[u][0] = faa; fa[u] = faa; dep[u] = dep[faa] + 1; pre[u] = ++dfs_clock; id[dfs_clock] = u; for (ll i = head[u]; i; i = nextt[i]) { ll v = to[i]; if (v == faa) continue; dfs(v,u); } endd[u] = dfs_clock; } ll query(ll o,ll l,ll r,ll x,ll y) { if (x <= l && r <= y) return sum[o]; pushdown(o); ll mid = (l + r) >> 1,res = 0; if (x <= mid) res += query(o * 2,l,mid,x,y); if (y > mid) res += query(o * 2 + 1,mid + 1,r,x,y); return res; } ll jump(ll x,ll y) { for (ll i = 19; i >= 0; i--) if (dep[fa2[x][i]] >= y) x = fa2[x][i]; return x; } void modify(ll x,ll v) { if (pre[root] < pre[x] || pre[root] > endd[x]) update(1,1,n,pre[x],endd[x],v); else { ll temp = jump(root,dep[x] + 1); if (pre[temp] != 1) update(1,1,n,1,pre[temp] - 1,v); if (endd[temp] != n) update(1,1,n,endd[temp] + 1,n,v); } } bool is_root(ll x) { return son[fa[x]][0] != x && son[fa[x]][1] != x; } bool get(ll x) { return son[fa[x]][1] == x; } void Pushdown(ll x) { if (rev[x]) { rev[son[x][0]] ^= 1; rev[son[x][1]] ^= 1; rev[x] = 0; swap(son[x][0],son[x][1]); } } ll findl(ll x) { Pushdown(x); while (son[x][0]) { x = son[x][0]; Pushdown(x); } return x; } void turn(ll x) { ll y = fa[x]; ll z = fa[y]; ll temp = get(x); if (!is_root(y)) son[z][son[z][1] == y] = x; fa[x] = z; son[y][temp] = son[x][temp ^ 1]; fa[son[y][temp]] = y; son[x][temp ^ 1] = y; fa[y] = x; } void splay(ll x) { ll top = 0; sta[++top] = x; for (ll y = x;!is_root(y);y = fa[y]) sta[++top] = fa[y]; for (ll i = top; i >= 1; i--) Pushdown(sta[i]); for (ll temp;!is_root(x);turn(x)) { if (!is_root(temp = fa[x])) { if (get(temp) == get(x)) turn(temp); else turn(x); } } } void Access(ll x) { ll t = 0; for (;x;t = x,x = fa[x]) { splay(x); if (t) { ll temp = findl(t); modify(temp,-1); } if (son[x][1]) { ll temp = findl(son[x][1]); modify(temp,1); } son[x][1] = t; } } void Reverse(ll x) { Access(x); splay(x); rev[x] ^= 1; root = x; } void Query(ll x) { if (x == root) { double temp = 1.0 * query(1,1,n,1,n) / (double)n; printf("%.10lf ",temp); return; } if (pre[root] < pre[x] || pre[root] > endd[x]) { double temp = 1.0 * query(1,1,n,pre[x],endd[x]) / (double)(endd[x] - pre[x] + 1); printf("%.10lf ",temp); return; } ll temp = jump(root,dep[x] + 1),sizee = 0; double tmp = 0.0; if (pre[temp] != 1) tmp += query(1,1,n,1,pre[temp] - 1),sizee += (pre[temp] - 1); if (endd[temp] != n) tmp += query(1,1,n,endd[temp] + 1,n),sizee += (n - endd[temp]); tmp /= (double)sizee; printf("%.10lf ",tmp); } int main() { root = 1; scanf("%lld%lld",&n,&m); for (ll i = 1; i < n; i++) { ll x,y; scanf("%lld%lld",&x,&y); add(x,y); add(y,x); } dfs(1,0); for (ll j = 1; j <= 19; j++) for (ll i = 1; i <= n; i++) fa2[i][j] = fa2[fa2[i][j - 1]][j - 1]; build(1,1,n); for (ll i = 1; i <= m; i++) { char ch[10]; ll x; scanf("%s",ch); scanf("%lld",&x); if (ch[2] == 'L') Access(x); if (ch[2] == 'C') Reverse(x); if (ch[2] == 'Q') Query(x); } return 0; }