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

原题干(由于是权限题我就直接砸出原题干了,要看题意概述的话在下面):

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<=100000,M<=100000.

题意概述:给出一棵树,初始每个结点有不同的颜色。现在支持三种操作:1.把某个结点的颜色改成一个之前都没有出现过的颜色,并将这个点到当前树根路径上的所有点全部改成这个颜色;2.改变当前的树根到另外一个点,并对原来的树根进行一次操作1;3.把询问当前形态的树中对一个点的子树中所有点进行操作1的平均代价(操作1代价的计算方式:这个点到当前树根路径上的不同颜色数量)。

从原题干来分析,相同的颜色一定连续出现在一条链上。再仔细想想,这样的操作正是LCT中的access操作!而每一次操作的代价就是access的时候经历的虚边的数量。如果是单点询问的话这就是一道LCT的裸题,但是问题在于题目要求的是子树询问。对子树信息的维护很容易想到用DFS序配合线段树来搞定。(于是就有两种流派:线段树单独在外面维护子树信息和就在LCT中维护子树信息两种,我选择线段树,常数小啊!如果要Link和Cut的话大不了我再来个LCT维护一下DFS序。。。)

分析没有换根操作的情况,在access操作中,令函数调用的时候的对象为s,当前所在结点为x,上一次所在的splay的根结点为y。y所在的splay就是s->y所在splay权值(真实树中深度)最小的点的一条链。我们找到链顶,可以发现这个链顶的子树中所有及结点在这一次access跳跃之后答案都要-1(每个点到根上的路径都少了一种颜色),x在当前树中位于splay维护的链上的儿子的子树中所有结点的答案+1。一路维护上去所有被影响的点的答案都被更新了。

换根的情况?可以发现在没有换根操作的时候可以保证对每个点的子树进行操作的时候一定是对应dfs序中一段连续的序列,但是换根之后就可以出现当前某个点子树在以1为根的dfs序中序列不连续,但是实际上也可以发现就是被分成了两端,x的子树对应的dfs序就是把x当前的父亲从dfs序中挖掉剩下的部分。对于是不是要挖点的判断借助DFS序就可以做到了。

随便分析几下思路就有了,也不是很难码,关键是细节,各种情况考虑全(为了节省您的时间请一定把一切想清楚了再开始码)。。。。。。这里只说一点:注意对LCT每个点的儿子的正确维护!

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cstdlib>
  5 #include<algorithm>
  6 #include<cmath>
  7 #include<queue>
  8 #include<set>
  9 #include<map>
 10 #include<vector>
 11 #include<cctype>
 12 using namespace std;
 13 const int MAXN=100005;
 14 typedef long long LL;
 15
 16 int N,M,root_now;
 17 struct edge{ int to,next; }E[MAXN<<1];
 18 int first[MAXN],np,val[MAXN],dfs_clock,l[MAXN],r[MAXN];
 19 struct segment_tree{
 20     static const int maxn=200005;
 21     int rt,np,lc[maxn],rc[maxn]; LL sum[maxn],add[maxn];
 22     void pushup(int now) { sum[now]=sum[lc[now]]+sum[rc[now]]; }
 23     void pushdown(int now,int L,int R)
 24     {
 25         if(!add[now]) return;
 26         int m=L+R>>1;
 27         add[lc[now]]+=add[now],sum[lc[now]]+=add[now]*(m-L+1);
 28         add[rc[now]]+=add[now],sum[rc[now]]+=add[now]*(R-m);
 29         add[now]=0;
 30     }
 31     void build(int &now,int L,int R,int *a)
 32     {
 33         now=++np;
 34         lc[now]=rc[now]=sum[now]=add[now]=0;
 35         if(L==R){ sum[now]=a[L]; return;}
 36         int m=L+R>>1;
 37         build(lc[now],L,m,a); build(rc[now],m+1,R,a);
 38         pushup(now);
 39     }
 40     void update(int now,int L,int R,int A,int B,int v)
 41     {
 42         if(A<=L&&R<=B){
 43             add[now]+=v,sum[now]+=(R-L+1)*v;
 44             return;
 45         }
 46         pushdown(now,L,R);
 47         int m=L+R>>1;
 48         if(B<=m) update(lc[now],L,m,A,B,v);
 49         else if(A>m) update(rc[now],m+1,R,A,B,v);
 50         else update(lc[now],L,m,A,B,v),update(rc[now],m+1,R,A,B,v);
 51         pushup(now);
 52     }
 53     LL query(int now,int L,int R,int A,int B)
 54     {
 55         if(A<=L&&R<=B) return sum[now];
 56         pushdown(now,L,R);
 57         int m=L+R>>1;
 58         if(B<=m) return query(lc[now],L,m,A,B);
 59         if(A>m) return query(rc[now],m+1,R,A,B);
 60         return query(lc[now],L,m,A,B)+query(rc[now],m+1,R,A,B);
 61     }
 62 }st;
 63 struct link_cut_tree{
 64     static const int maxn=100005;
 65     struct node{
 66         int fa,ch[2]; bool rev;
 67         node(){ fa=ch[0]=ch[2]=0,rev=0; }
 68     }nd[maxn];
 69     void link(int x,int d,int y) { nd[x].ch[d]=y,nd[y].fa=x; }
 70     bool isrt(int x) { return nd[nd[x].fa].ch[0]!=x&&nd[nd[x].fa].ch[1]!=x; }
 71     void pushdown(int x)
 72     {
 73         if(!nd[x].rev) return;
 74         int lc=nd[x].ch[0],rc=nd[x].ch[1];
 75         if(lc) nd[lc].rev^=1,swap(nd[lc].ch[0],nd[lc].ch[1]);
 76         if(rc) nd[rc].rev^=1,swap(nd[rc].ch[0],nd[rc].ch[1]);
 77         nd[x].rev=0;
 78     }
 79     void rot(int x)
 80     {
 81         int y=nd[x].fa,z=nd[y].fa;
 82         pushdown(y);pushdown(x);
 83         int d=nd[y].ch[0]==x;
 84         if(!isrt(y)) link(z,y==nd[z].ch[1],x); nd[x].fa=z;
 85         link(y,d^1,nd[x].ch[d]);
 86         link(x,d,y);
 87     }
 88     void splay(int x)
 89     {
 90         pushdown(x);
 91         while(!isrt(x)){
 92             int y=nd[x].fa,z=nd[y].fa;
 93             if(!isrt(y)) rot((x==nd[y].ch[0])==(y==nd[z].ch[0])?y:x);
 94             rot(x);
 95         }
 96     }
 97     int find(int x,int d)
 98     {
 99         while(nd[x].ch[d]){ pushdown(x),x=nd[x].ch[d]; }
100         return x;
101     }
102     void access(int x)
103     {
104         int y=0,xx,yy;
105         while(x){
106             splay(x);
107             if(y){
108                 yy=find(y,0);
109                 if(l[x]<l[yy]&&l[yy]<=r[x]) st.update(st.rt,1,N,l[yy],r[yy],-1);
110                 else{
111                     if(l[x]>1) st.update(st.rt,1,N,1,l[x]-1,-1);
112                     if(r[x]<N) st.update(st.rt,1,N,r[x]+1,N,-1);
113                 }
114             }
115             pushdown(x);
116             if((xx=find(nd[x].ch[1],0))&&l[x]<l[xx]&&l[xx]<=r[x]) st.update(st.rt,1,N,l[xx],r[xx],1);
117             else if(xx){
118                 if(l[x]>1) st.update(st.rt,1,N,1,l[x]-1,1);
119                 if(r[x]<N) st.update(st.rt,1,N,r[x]+1,N,1);
120             }
121             nd[x].ch[1]=y,y=x,x=nd[x].fa;
122         }
123     }
124     void mroot(int x)
125     {
126         access(x); splay(x);
127         nd[x].rev^=1,swap(nd[x].ch[0],nd[x].ch[1]);
128     }
129 }lct;
130
131 void add_edge(int u,int v)
132 {
133     E[++np]=(edge){v,first[u]};
134     first[u]=np;
135 }
136 void data_in()
137 {
138     scanf("%d%d",&N,&M);
139     int x,y;
140     for(int i=1;i<N;i++){
141         scanf("%d%d",&x,&y);
142         add_edge(x,y); add_edge(y,x);
143     }
144 }
145 void DFS(int i,int f,int d)
146 {
147     l[i]=++dfs_clock,val[l[i]]=d;
148     for(int p=first[i];p;p=E[p].next){
149         int j=E[p].to;
150         if(j==f) continue;
151         lct.nd[j].fa=i;
152         DFS(j,i,d+1);
153     }
154     r[i]=dfs_clock;
155 }
156 void work()
157 {
158     DFS(root_now=1,0,1);
159     st.build(st.rt,1,N,val);
160     char op[10]; int x,len; double ans;
161     for(int i=1;i<=M;i++){
162         scanf("%s%d",op,&x);
163         if(op[2]==‘L‘) lct.access(x);
164         else if(op[2]==‘C‘) lct.mroot(root_now=x);
165         else if(op[2]==‘Q‘){
166             ans=0,len=0;
167             if(l[x]<=l[root_now]&&r[root_now]<=r[x]){
168                 lct.splay(x);
169                 x=lct.nd[x].ch[0]?lct.find(lct.nd[x].ch[0],1):lct.nd[x].fa;
170                 if(l[x]>1) ans+=1.0*st.query(st.rt,1,N,1,l[x]-1),len+=l[x]-1;
171                 if(r[x]<N) ans+=1.0*st.query(st.rt,1,N,r[x]+1,N),len+=N-r[x];
172                 ans/=len;
173             }
174             else ans=1.0*st.query(st.rt,1,N,l[x],r[x])/(r[x]-l[x]+1);
175             printf("%.10f\n",ans);
176         }
177     }
178 }
179 int main()
180 {
181     freopen("test.in","r",stdin);
182     freopen("test.out","w",stdout);
183     data_in();
184     work();
185     return 0;
186 }

原文地址:https://www.cnblogs.com/KKKorange/p/8494283.html

时间: 2024-10-28 15:59:06

BZOJ 3779 重组病毒 LCT+线段树(维护DFS序)的相关文章

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

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

CF877E Danil and a Part-time Job 线段树维护dfs序

\(\color{#0066ff}{题目描述}\) 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 pow 将一个点 x 的子树中所有节点取反 对于每个 get 给出答案 \(\color{#0066ff}{输入格式}\) 第一行一个整数 n 第二行共 n?1 个整数,第 i 个数 \(x_i\) 表示 \(x_i\) 是 i+1 的父亲, 第三行给出每个点的初始权值 第四行一个整数 m 接下来

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

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

CodeForces 343D 线段树维护dfs序

给定一棵树,初始时树为空 操作1,往某个结点注水,那么该结点的子树都注满了水 操作2,将某个结点的水放空,那么该结点的父亲的水也就放空了 操作3,询问某个点是否有水 我们将树进行dfs, 生成in[u], 访问结点u的时间戳,out[u],离开结点u的时间戳 每个结点的in值对应在线段树中的区间的一点 那么对于操作1, 只要将区间[in[u],out[u]] 的值都改为1, 但是如果区间[in[u],out[u]] 原先存在为0的点,那么父区间肯定是空的,这个操作不能 改变父区间的状态,所以需要

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

3779: 重组病毒 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 224  Solved: 95[Submit][Status][Discuss] Description 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力极强.为了阻止这种病毒传播,某安全机构策划了一次实验,来研究这种病毒.实验在一个封闭的局域网内进行.局域网内有n台计算机,编号为1~n.一些计算机之间通过网线直接相连,形

3779: 重组病毒

传送门 一道很有奥妙的题. 发现修改操作和access有着不可不说的关系. 因为操作的特殊性,每种颜色在树上是一段连续的区间.不会被切开. 若x跟父亲颜色相同,设x到父亲的边的权值为0,否则为1 那么一个点到根的颜色就是它到跟路径上1的个数+1 修改从x到根,x到根的边全都变成0,x到跟路径上相邻的其他边都变成1 把1看成虚边,0看出实边,就相当于access操作 用lct维护即可得到每次需要修改的边,修改一条边相当于子树修改,线段树维护dfs序即可 newroot时候不需要单独处理,只需acc

CCPC河南省赛B-树上逆序对| 离线处理|树状数组 + 线段树维护逆序对 + dfs序 + 离散化

B题地址:树上逆序对 有两个思路 方法一:线段树离线 + 树状数组或者线段树维护区间和 0:离散化,离线存储输入的operation操作序列. ①:先线段树在dfs序上离线处理好整一棵树:在dfs序上先查询"加入当前结点的逆序对权值和"并记录,再加入当前这个节点:dfs完毕后,就已经记录好每个结点的dfs序出入时间戳(转化成区间问题了)和每个 ②:使用树状数组或者新的线段树在dfs序上插入逆序对权值 为什么能这样呢?因为dfs序维护了每个结点遍历的顺序,每个结点的dfs序时间戳肯定比它

[BZOJ 1018] [SHOI2008] 堵塞的交通traffic 【线段树维护联通性】

题目链接:BZOJ - 1018 题目分析 这道题就说明了刷题少,比赛就容易跪..SDOI Round1 Day2 T3 就是与这道题类似的..然而我并没有做过这道题.. 这道题是线段树维护联通性的经典模型. 我们线段树的一个节点表示一个区间的联通性,有 6 个 bool 值,表示这个区间的 4 个角上的点之间的联通性. 然后用两个子区间的联通性和两个子区间之间的连边情况合并出整个区间的联通性. 修改某条边时,先在边的数组中修改,然后从这条边所在的点的线段树叶子开始向上 Update . 询问两

[BZOJ 3995] [SDOI2015] 道路修建 【线段树维护连通性】

题目链接:BZOJ - 3995 题目分析 这道题..是我悲伤的回忆.. 线段树维护连通性,与 BZOJ-1018 类似,然而我省选之前并没有做过  1018,即使它在 ProblemSet 的第一页. 更悲伤的是,这道题有 40 分的暴力分,写个 Kruskal 就可以得到,然而我写了个更快的 DP . 这本来没有什么问题,然而我的 DP 转移少些了一种情况,于是...爆零.没错,省选前20名可能就我没有得到这 40 分? 不想再多说什么了...希望以后不要再这样 SB 了,如果以后还有机会的