【算法】Link-Cut Tree+线段树(维护DFS序)

【题解】整整三天……T_T

这篇题解比较资瓷:permui

这道题虽然树形态没有变化,但用lct写的原因在于把题目中的操作一进行了神转化:每条重链表示一种颜色,点到根的颜色数=经过的轻链数+1

询问一个点的子树所有结点到根的代价和(的平均数),子树问题考虑使用dfs序维护。有要支持子树整体的加减,所以用线段树维护DFS序。

操作一转化之后就相当于access操作(x到根染成一种颜色==x到根在同一重链),过程中轻变重子树-1,重变轻子树+1,这里的子树是相对于新根的,做法见下。

操作二换根是关键。题目条件刚好先做了access(x)操作,于是我们就可以很方便地换根。问题在于DFS序根据的树形态从一开始就固定下来,那么根据新根root和查询点x在树上的位置关系可以分类讨论:

1.root=x,整棵树。

2.root在x的子树上,整棵树除去root所在子树。

3.x在root的子树上,原x子树。

【注意】

1.代价和很大,long long

2.线段树放越界:if(l>r)return;

  下传之前判断是否叶子结点

  mid是seg的l和r的中点

3.无向边数组开2倍。

3.邻接表记得判父亲。

4.利用DFS序嵌套关系判断子结点。

5.splay上访问前要先下传。

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
using namespace std;
const int maxn=;
int f[maxn],t[maxn][],dfsnum=,first[maxn],second[maxn],next[maxn],tot,deep[maxn],g[maxn],root,n,m,father[maxn];
struct edge{int u,v,from;}e[maxn*];//无向边数组开两倍!
struct tree{int l,r;long long sum,delta;}tr[maxn*];
int read()
{
char c;int s=;
while(!isdigit(c=getchar()));
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s;
}
void insert(int u,int v)
{tot++;e[tot].u=u;e[tot].v=v;e[tot].from=next[u];next[u]=tot;}
void seg_build(int k,int l,int r)
{
tr[k].l=l;tr[k].r=r;
if(l==r){tr[k].sum=;return;}
int mid=(l+r)>>;
seg_build(k<<,l,mid);
seg_build(k<<|,mid+,r);
}
void seg_ins(int k,int x)
{tr[k].sum+=1ll*(tr[k].r-tr[k].l+)*x;}
void seg_pushdown(int k)
{
if(tr[k].delta)
{
tr[k<<].delta+=tr[k].delta;
tr[k<<|].delta+=tr[k].delta;
seg_ins(k<<,tr[k].delta);
seg_ins(k<<|,tr[k].delta);
tr[k].delta=;
}
}
void seg_insert(int k,int l,int r,int x)
{
if(l>r)return;//woc!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// if(tr[k].l==tr[k].r&&l!=tr[k].l&&r!=tr[k].r){return;}//这样写是错的!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
if(l<=tr[k].l&&tr[k].r<=r)
{
seg_ins(k,x);
tr[k].delta+=1ll*x;
return;
}
else
{
if(tr[k].l==tr[k].r)return;//防止非法访问!!!但是其实在区间的中间(l+1)也可能出现叶子结点的,所以不能一概而论,只能在这里判断。
seg_pushdown(k);
int mid=(tr[k].l+tr[k].r)>>;
if(l<=mid)seg_insert(k<<,l,r,x);
if(r>mid)seg_insert(k<<|,l,r,x);
tr[k].sum=tr[k<<].sum+tr[k<<|].sum;
}
}
long long seg_query(int k,int l,int r)
{
if(l>r)return ;//woc!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// if(tr[k].l==tr[k].r&&l!=tr[k].l&&r!=tr[k].r)return 0;//错误写法!
if(l<=tr[k].l&&tr[k].r<=r)return tr[k].sum;
if(tr[k].l==tr[k].r)return ;
seg_pushdown(k);
int mid=(tr[k].l+tr[k].r)>>;//mid是seg的中点!
long long sums=;
if(l<=mid)sums=seg_query(k<<,l,r);
if(r>mid)sums+=seg_query(k<<|,l,r);
return sums;
}
void dfs(int x,int fa)
{
father[x]=fa;
f[x]=fa;//开始时每个点自成重链,但是初始根为1,所以父子关系已经串好了。
first[x]=++dfsnum;
deep[x]=deep[fa]+;
seg_insert(,first[x],first[x],deep[x]);
for(int i=next[x];i;i=e[i].from)if(e[i].v!=fa)//判父亲!!!
{
//这棵树的父亲和splay的父亲不能混为一谈。
dfs(e[i].v,x);
}
second[x]=dfsnum;
}
bool isroot(int x)
{return !x||(t[f[x]][]!=x&&t[f[x]][]!=x);}//0也认为它是splay的根。
void pushdown(int x)
{
if(g[x])
{
g[t[x][]]^=;g[t[x][]]^=;
swap(t[x][],t[x][]);
g[x]=;
}
}
void rotate(int x)
{
int k=x==t[f[x]][];
int y=f[x];
t[y][k]=t[x][!k];f[t[x][!k]]=y;
if(!isroot(y))t[f[y]][y==t[f[y]][]]=x;f[x]=f[y];f[y]=x;
t[x][!k]=y;
}
int cnt,N[maxn];
void splay(int x)
{
cnt=;
int y=x;
while(!isroot(y))N[++cnt]=y,y=f[y];
pushdown(y);
for(int i=cnt;i>=;i--)pushdown(N[i]);
while(!isroot(x))
{
if(isroot(f[x])){rotate(x);return;}
int X=x==t[f[x]][],Y=f[x]==t[f[f[x]]][];
if(X^Y)rotate(x),rotate(x);
else rotate(f[x]),rotate(x);
}
}
bool inson(int x,int y)//x在y的子树内
{return first[x]>=first[y]&&second[x]<=second[y];}
int wson(int x,int y)//x在y的哪个儿子的子树内
{
for(int i=next[y];i;i=e[i].from)if(e[i].v!=father[y])
{
if(first[x]>=first[e[i].v]&&second[x]<=second[e[i].v])return e[i].v;
}
return ;
}
void lct_insert(int x,int num)
{
if(x==root)seg_insert(,,n,num);else
if(inson(root,x))
{
int p=wson(root,x);
seg_insert(,,first[p]-,num);
seg_insert(,second[p]+,n,num);
}
else seg_insert(,first[x],second[x],num);
}
int top(int x)
{
pushdown(x);
while(t[x][])x=t[x][],pushdown(x);//访问子节点要下传啊!!!
return x;
}
void access(int x)
{
int y=;
while(x)
{
splay(x);
if(t[x][])lct_insert(top(t[x][]),);//随时要注意判断结点是否存在!
if(y)lct_insert(top(y),-);//root一定是主链的根(最左端结点),往主链方向靠近的话,重链顶部结点的子树就能自然地覆盖包括整条链。
t[x][]=y;
y=x;x=f[x];
}
}
void center(int x)
{
splay(x);//必须splay才能定位到主链,方便翻转。
root=x;
g[x]^=;//使x成为主链的根,因为access中已经splay了使x成为主链splay的根节点,对x操作就是对主链操作。
}
double query(int x)
{
if(x==root){return 1.0*seg_query(,,n)/n;}else
if(inson(root,x))
{
int p=wson(root,x);
return 1.0*(seg_query(,,first[p]-)+seg_query(,second[p]+,n))/(n-(second[p]-first[p]+));
}
else return 1.0*seg_query(,first[x],second[x])/(second[x]-first[x]+);
}
char s[];
int main()
{
n=read();m=read();
int u,v;
for(int i=;i<n;i++)
{ u=read(),v=read();
insert(u,v);
insert(v,u);
}
root=;//初值!
seg_build(,,n);
dfs(,);
int x;
for(int i=;i<=m;i++)
{
scanf("%s%d",s,&x);
if(s[]=='Q')printf("%.10lf\n",query(x));
else
{
access(x);
if(s[]=='C')center(x);
}
}
return ;
}

【BZOJ】3779 重组病毒的更多相关文章

  1. bzoj 3779: 重组病毒 LCT+线段树+倍增

    题目: 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力极强.为了阻止这种病毒传播,某安全机构策划了一次实验,来研究这种病毒. 实验在一个封闭 ...

  2. BZOJ 3779: 重组病毒(线段树+lct+树剖)

    题面 escription 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力极强.为了阻止这种病毒传播,某安全机构策划了一次实验,来研究这种病 ...

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

    原题干(由于是权限题我就直接砸出原题干了,要看题意概述的话在下面): Description 黑客们通过对已有的病毒反编译,将许多不同的病毒重组,并重新编译出了新型的重组病毒.这种病毒的繁殖和变异能力 ...

  4. bzoj 3779 重组病毒——LCT维护子树信息

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779 调了很久……已经懒得写题解了.https://www.cnblogs.com/Zinn ...

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

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779 RELEASE操作可以对应LCT的 access,RECENTER则是 makeroo ...

  6. bzoj 3779: 重组病毒【LCT+线段树维护dfs序】

    %.8lf会WA!!%.8lf会WA!!%.8lf会WA!!要%.10lf!! 和4817有点像,但是更复杂. 首先对于操作一"在编号为x的计算机中植入病毒的一个新变种,在植入一个新变种时, ...

  7. bzoj 3779: 重组病毒

    一道好题~~ 一个点到根传染需要的时间是这段路径上不同颜色的数目,一个点子树到根平均传染时间就是加权平均数了(好像是废话). 所以只要用线段树维护dfs序就这个可以了,换根的话一个点的子树要么在dfs ...

  8. bzoj 3779 重组病毒 好题 LCT+dfn序+线段树分类讨论

    题目大意 1.将x到当前根路径上的所有点染成一种新的颜色: 2.将x到当前根路径上的所有点染成一种新的颜色,并且把这个点设为新的根: 3.查询以x为根的子树中所有点权值的平均值. 分析 原题codec ...

  9. BZOJ 3779 重组病毒 ——LCT 线段树

    发现操作一很像一个LCT的access的操作. 然后答案就是路径上的虚边的数量. 然后考虑维护每一个点到根节点虚边的数量, 每次断开一条偏爱路径的时候,子树的值全部+1, 连接一条偏爱路径的时候,子树 ...

随机推荐

  1. from module import 和 import 的区别

    最近在用codecademy学python,遇到一些题目错误,小小记录一下 如from math import sqrt是把sqrt作为本文件的方法导入进来了,使用的时候只需要直接调用sqrt. 而如 ...

  2. ubuntu上的inpack测试

    测试linpack 配置 配置linpack环境是整个过程中最麻烦的,也可能是因为我在配置的过程中出现了很多小问题吧.大概有3天的时间除了上课就在配置环境. 问题 总结起来问题和解决方法有这些 1.路 ...

  3. [计算机网络] DNS何时使用TCP协议,何时使用UDP协议

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类.但很少有人知道DNS分别在什么情况下使用这两种协议. 先简单介绍下TCP与UDP. ...

  4. coreldraw x5提示盗版警告解决方法

    CorelDRAW是一款图形图像软件,大多数用户使用的都是coreldraw x5破解版,所以基本上都收到了coreldraw x5提示盗版警告,导致不能用,没关系,绿茶小编有解决方法. coreld ...

  5. Delphi:ADOConnection连接SQLServer自动断网问题解决

    =============================== 解决方法一:异常时关闭连接,WinXP,win7 32位大部分情况都是起作用的,不过在有些windows操作系统下(如家庭版)不起作用, ...

  6. 判断IP地址是否在指定范围内的方法

    比如给定一个ip段:127.0.0.1 ~ 127.0.0.255,我们想判断一个给定的ip地址是否在此段内,可以先将ip地址转换成整数,然后整数比较大小就很容易了. 例如: 127.0.0.1 = ...

  7. Java入门之:对象和类

    Java对象和类 Java作为一种面向对象语言,支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 本节我们重点研究对象和类的概念: 对象: 对象是类的一个实例,有状态和行为.例如 ...

  8. hdu 2151 Worm (DP)

    Worm Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  9. Windows系统Unity3D中的快捷键

    Windows系统Unity3D中的快捷键 组合键 键 功能 File 文件 Ctrl   N New Scene 新建场景 Ctrl   O Open Scene 打开场景 Ctrl   S Sav ...

  10. P2672 推销员 优先队列 + 贪心

    ---题面--- 题解: 我会说我想这道普及组题都想了好久么.... 不熟练的普及组选手.jpg 最后随便猜了一个结论居然是对的... 放结论: 假设x = i的最优决策为f[i], 那么f[i + ...