• 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
    ",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;
    }
  • 相关阅读:
    REST Security with JWT using Java and Spring Security
    UserMapper.selectByPrimaryKey-Inline 报错的解决办法
    Nginx反向代理,负载均衡,redis session共享,keepalived高可用
    HTML 5 Web 存储-localStorage
    Android之自定义checkbox样式
    android fragment传递参数_fragment之间传值的两种方法
    linux常用基本命令
    fragment点击跳转到外部Activity后,怎么通过返回按钮返回
    android 中FragmentActivity中模拟返回键返回上一个Activity效果
    Fragment与Activity相互传递数据:
  • 原文地址:https://www.cnblogs.com/zbtrs/p/8539091.html
Copyright © 2020-2023  润新知