bzoj3779 重组病毒

3779: 重组病毒

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 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操作的存在,这个集合并不一定是始终不变的。
至此,安全机构认为已经不需要实际的实验了,于是他们拜托你编写一个程序,模拟实验的结果,并回答所有的询问。

Input

输入的第一行包含两个整数n和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

Sample Output

4.0000000000
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\n",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\n",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\n",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;
}

 

posted @ 2018-03-10 14:28  zbtrs  阅读(252)  评论(0编辑  收藏  举报