【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序

3779: 重组病毒

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 224  Solved: 95
[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

Source

Solution

这道题真是容易写残调半天。

把题目转化一下发现这些过程实际上类似于LinkCutTree,具体的三个操作分别是:

1、将x到根路径上的所有点染成一种新的颜色; 
2、将x到根路径上的所有点染成一种新的颜色,并且把这个点设为根; 
3、查询以x为根的子树中所有点到根 虚边数+1 的平均值。

然后1对应Access,2对应makeroot,3维护子树和。

LinkCutTree并不支持维护子树和,所以用dfs序+线段树维护,在每次Access的时候会对相应节点子树+1\-1,这个用线段树很好实现。

然后至于换根对线段树的影响,就一如既往的讨论三种情况即可。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define LL long long
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while (ch<‘0‘ || ch>‘9‘) {if (ch==‘-‘) f=-1; ch=getchar();}
    while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘; ch=getchar();}
    return x*f;
}
#define MAXN 100010
int N,M;
struct EdgeNode{int next,to,from;}edge[MAXN<<1];
int head[MAXN],cnt=1;
inline void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v; edge[cnt].from=u;}
inline void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);}
int pl[MAXN],dfn,pre[MAXN],pr[MAXN],root,father[17][MAXN],deep[MAXN];
LL val[MAXN];
inline void DFS(int now,int last)
{
    pl[now]=++dfn; pre[dfn]=now; val[now]=val[last]+1;
    for (int i=1; i<=16; i++)
        if (deep[now]>=(1<<i)) father[i][now]=father[i-1][father[i-1][now]];
            else break;
    for (int i=head[now]; i; i=edge[i].next)
        if (edge[i].to!=last)
            father[0][edge[i].to]=now,
            deep[edge[i].to]=deep[now]+1,
            DFS(edge[i].to,now);
    pr[now]=dfn;
}
inline int LCA(int x,int y)
{
    if (deep[x]<deep[y]) swap(x,y);
    int dd=deep[x]-deep[y];
    for (int i=0; i<=16; i++) if (dd&(1<<i)) x=father[i][x];
    for (int i=16; i>=0; i--)
        if (father[i][x]!=father[i][y])
            x=father[i][x],y=father[i][y];
    return x==y? x:father[0][x];
}
inline int Jump(int x,int k)
{
    for (int i=0; i<=16; i++)
        if (k&(1<<i)) x=father[i][x];
    return x;
}
namespace SgtTree
{
    LL tree[MAXN<<2],tag[MAXN<<2]; int size[MAXN<<2];
    inline void Update(int now) {tree[now]=tree[now<<1]+tree[now<<1|1];}
    inline void Add(int now,LL val) {tree[now]+=(LL)size[now]*val; tag[now]+=val;}
    inline void PushDown(int now)
    {
        if (!tag[now] || size[now]==1) return;
        Add(now<<1,tag[now]); Add(now<<1|1,tag[now]);
        tag[now]=0;
    }
    inline void Build(int now,int l,int r)
    {
        size[now]=r-l+1;
        if (l==r) {tree[now]=val[pre[l]]; return;}
        int mid=(l+r)>>1;
        Build(now<<1,l,mid); Build(now<<1|1,mid+1,r);
        Update(now);
    }
    inline void Modify(int now,int l,int r,int L,int R,LL val)
    {
        if (L>R) return;
        PushDown(now);
        if (L<=l && R>=r) {Add(now,val); return;}
        int mid=(l+r)>>1;
        if (L<=mid) Modify(now<<1,l,mid,L,R,val);
        if (R>mid) Modify(now<<1|1,mid+1,r,L,R,val);
        Update(now);
    }
    inline LL QueryT(int now,int l,int r,int L,int R)
    {
        if (L>R) return 0;
        PushDown(now);
        if (L<=l && R>=r) return tree[now];
        int mid=(l+r)>>1; LL re=0;
        if (L<=mid) re+=QueryT(now<<1,l,mid,L,R);
        if (R>mid) re+=QueryT(now<<1|1,mid+1,r,L,R);
        return re;
    }
    inline int QueryS(int now,int l,int r,int L,int R)
    {
        if (L>R) return 0;
        PushDown(now);
        if (L<=l && R>=r) return size[now];
        int mid=(l+r)>>1,re=0;
        if (L<=mid) re+=QueryS(now<<1,l,mid,L,R);
        if (R>mid) re+=QueryS(now<<1|1,mid+1,r,L,R);
        return re;
    }
    inline double Query(int now)
    {
        if (!now) return 0;
        LL sum=0; int x,y,sz=0;
        if (now==root)
            sz=QueryS(1,1,N,pl[1],pr[1]),sum=QueryT(1,1,N,pl[1],pr[1]);
        else
            {
                x=LCA(root,now);
                if (x!=now)
                    sz=QueryS(1,1,N,pl[now],pr[now]),sum=QueryT(1,1,N,pl[now],pr[now]);
                else
                    y=Jump(root,deep[root]-deep[now]-1),
                    sz=QueryS(1,1,N,1,pl[y]-1)+QueryS(1,1,N,pr[y]+1,N),
                    sum=QueryT(1,1,N,1,pl[y]-1)+QueryT(1,1,N,pr[y]+1,N);
            }
        return 1.0*sum/sz;
    }
    inline void Modify(int now,LL val)
    {
        if (!now) return;
        int x,y;
        if (now==root) Modify(1,1,N,pl[1],pr[1],val);
        else
            {
                x=LCA(root,now);
                if (x!=now)
                    Modify(1,1,N,pl[now],pr[now],val);
                 else
                    y=Jump(root,deep[root]-deep[now]-1),
                    Modify(1,1,N,1,pl[y]-1,val),Modify(1,1,N,pr[y]+1,N,val);
            }
    }
}using namespace SgtTree;
namespace LCT
{
    int fa[MAXN],son[MAXN][2],left[MAXN],right[MAXN]; bool rev[MAXN];
    inline bool is_root(int x) {return !fa[x] || son[fa[x]][0]!=x&&son[fa[x]][1]!=x;}
    inline void Pushup(int x)
    {
        if (!x) return;
        left[x]=right[x]=x;
        if (son[x][0]) left[x]=left[son[x][0]];
        if (son[x][1]) right[x]=right[son[x][1]];
    }
       inline void Rev(int x) {if (!x) return; swap(left[x],right[x]),swap(son[x][0],son[x][1]),rev[x]^=1;}
    inline void Pushdown(int x) {if (!x) return; if (rev[x]) Rev(son[x][0]),Rev(son[x][1]),rev[x]^=1;}
    inline void Rotate(int x)
    {
        int y=fa[x],w=son[y][1]==x,z=fa[y];
        son[y][w]=son[x][w^1];
        if (son[x][w^1]) fa[son[x][w^1]]=y;
        if (son[z][0]==y) son[z][0]=x; else if (son[z][1]==y) son[z][1]=x;
        fa[x]=z; fa[y]=x; son[x][w^1]=y; Pushup(y);
    }
    int stack[MAXN];
    inline void Splay(int x)
    {
        int t=x,top=0,y; stack[++top]=x;
        while (!is_root(t)) stack[++top]=t=fa[t];
        while (top) Pushdown(stack[top--]);
        while (!is_root(x))
            {
                y=fa[x];
                if (!is_root(y))
                    if ((son[fa[y]][0]==y)^(son[y][0]==x)) Rotate(x);
                        else Rotate(y);
                Rotate(x);
            }
        Pushup(x);
    }
    inline void Access(int x)
    {
        for (int y=0; x; y=x,x=fa[x])
            Splay(x),
            SgtTree::Modify(left[y],-1),
            SgtTree::Modify(left[son[x][1]],1),
            son[x][1]=y,Pushup(x);
    }
    inline void Makeroot(int x) {Access(x); Splay(x); Rev(x);}
}using namespace LCT;
int main()
{
    N=read(),M=read();
    for (int i=1,x,y; i<=N-1; i++) x=read(),y=read(),InsertEdge(x,y);
    DFS(root=1,0);
    SgtTree::Build(1,1,N);
    for (int i=1; i<=N; i++) LCT::fa[i]=father[0][i],LCT::left[i]=LCT::right[i]=i;
    while (M--)
        {
            char opt[10]; int x;
            scanf("%s",opt+1); x=read();
            switch (opt[3])
                {
                    case ‘L‘: LCT::Access(x); break;
                    case ‘C‘: LCT::Makeroot(x); root=x; break;
                    case ‘Q‘: printf("%.10lf\n",SgtTree::Query(x)); break;
                }
        }
    return 0;
}

突然想起char哥说这种题做的非常爽...然而,我因为线段树区间操作手贱达成了if (l==r)外加LCT中忘swap了一个地方两个脑残错误调了整整两节晚自习啊,感觉非常蛋疼;

这说明以后写数据结构,思路一定要清晰,写的时候一定不能图快,要不然Debug浪费的时间更多。

外加,自己常数怎么这么大啊,BZOJ成功倒数rank1......

时间: 2024-10-03 15:23:50

【BZOJ-3779】重组病毒 LinkCutTree + 线段树 + DFS序的相关文章

BZOJ 3779 重组病毒 LCT+线段树(维护DFS序)

原题干(由于是权限题我就直接砸出原题干了,要看题意概述的话在下面): Description 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力极强.为了阻止这种病毒传播,某安全机构策划了一次实验,来研究这种病毒.实验在一个封闭的局域网内进行.局域网内有n台计算机,编号为1~n.一些计算机之间通过网线直接相连,形成树形的结构.局域网中有一台特殊的计算机,称之为核心计算机.根据一些初步的研究,研究员们拟定了一个一共m步的实验.实验开始之前,核

BZOJ 3779 重组病毒 LCT+线段树维护DFS序

题目大意:给定一棵树,初始每个点都有一个颜色,支持三种操作: 1.将某个点到根的路径上所有点染上一种新的颜色 2.将某个点到根的路径上所有点染上一种新的颜色,然后把根设为这个点 3.定义一个点的代价为这个点到根路径上颜色的种类数,求某个点子树中所有点代价的平均值 我真是炖了狗了-- 容易发现这玩应就是个LCT,操作1就是Access,操作2就是Move_To_Root,代价就是一个点到根路径上的虚边数量+1 我们用LCT模拟上述操作,用线段树维护DFS序维护信息,一旦LCT中出现了虚实边的切换,

Tsinsen A1505. 树(张闻涛) 倍增LCA,可持久化线段树,DFS序

题目:http://www.tsinsen.com/A1505 A1505. 树(张闻涛) 时间限制:1.0s   内存限制:512.0MB 总提交次数:196   AC次数:65   平均分:58.62 将本题分享到: 查看未格式化的试题   提交   试题讨论 试题来源 2013中国国家集训队第二次作业 问题描述 给定一棵N个节点的树,每个点有一个权值,有M个询问(a,b,c)若a 为1,回答b到c路径上的最小权值,若a为2,回答b到c路径上的最大权值,若a为3,回答b到c路径上的所有权值的

Codeforces 384E 线段树+dfs序

题目链接:点击打开链接 题意: 给定n个点,m个询问的无向树(1为根) 下面n个数表示每个点的权值 下面n-1行给出树 操作1:x点权值+v, x的第 i & 1 的儿子-v, 第 !(i&1) 的儿子+v 操作2:询问x点权值 dfs把树转成序列 根据深度把点分成2组 分别用线段树维护.. 然后Y一下 #include<stdio.h> #include<string.h> #include<iostream> #include<algorith

线段树+dfs序(Apple Tree )(Assign the task )

线段树+dfs序 给定一棵n个节点的树,m次查询,每次查询需要求出某个节点深度为h的所有子节点. 作为预处理,首先将树的所有节点按深度保存起来,每个深度的所有节点用一个线性结构保存,每个深度的节点相对顺序要和前序遍历一致. 然后从树的根节点进行dfs,对于每个节点记录两个信息,一个是dfs进入该节点的时间戳in[id],另一个是dfs离开该节点的时间戳out[id]. 最后对于每次查询,求节点v在深度h的所有子节点,只需将深度为h并且dfs进入时间戳在in[v]和out[v]之间的所有节点都求出

HDU 5692 线段树+dfs序

Snacks Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 1779    Accepted Submission(s): 427 Problem Description 百度科技园内有n 个零食机,零食机之间通过n−1 条路相互连通.每个零食机都有一个值v ,表示为小度熊提供零食的价值. 由于零食被频繁的消耗和补充,零食机的价值v

bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779 RELEASE操作可以对应LCT的 access,RECENTER则是 makeroot: 考虑颜色数,把一条实边变成虚边,子树+1,虚变实子树-1: 但有换根操作,怎么维护子树? 也可以用 dfs 序线段树维护,其实换 rt 只是 splay 的根方向改变,对应的子树还是可以找到的: 注意虚边变实或实边变虚时要找子树,不是直接找那个儿子,而是找那个儿子所在 splay 的根: 然后

【bzoj3779】重组病毒 LCT+树上倍增+DFS序+树状数组区间修改区间查询

题目描述 给出一棵n个节点的树,每一个节点开始有一个互不相同的颜色,初始根节点为1. 定义一次感染为:将指定的一个节点到根的链上的所有节点染成一种新的颜色,代价为这条链上不同颜色的数目. 现有m次操作,每次为一下三种之一: RELEASE x:对x执行一次感染: RECENTER x:把根节点改为x,并对原来的根节点执行一次感染: REQUEST x:询问x子树中所有节点感染代价的平均值. 输入 输入的第一行包含两个整数n和m,分别代表局域网中计算机的数量,以及操作和询问的总数.接下来n-1行,

苹果树(线段树+Dfs序)

1228 苹果树 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 在卡卡的房子外面,有一棵苹果树.每年的春天,树上总会结出很多的苹果.卡卡非常喜欢吃苹果,所以他一直都精心的呵护这棵苹果树.我们知道树是有很多分叉点的,苹果会长在枝条的分叉点上面,且不会有两个苹果结在一起.卡卡很想知道一个分叉点所代表的子树上所结的苹果的数目,以便研究苹果树哪些枝条的结果能力比较强. 卡卡所知道的是,每隔一些时间,某些分叉点上会结出一些苹果,但