题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779

RELEASE操作可以对应LCT的 access,RECENTER则是 makeroot;

考虑颜色数,把一条实边变成虚边,子树+1,虚变实子树-1;

但有换根操作,怎么维护子树?

也可以用 dfs 序线段树维护,其实换 rt 只是 splay 的根方向改变,对应的子树还是可以找到的;

注意虚边变实或实边变虚时要找子树,不是直接找那个儿子,而是找那个儿子所在 splay 的根;

然后这里 splay 的 reverse 就要打上标记的同时已经交换子树,否则找儿子时可能会错(?);

一开始写了树剖+线段树的复杂版本,总是不对...

#include<cstdio>
#include<cstring>
#include<algorithm>
#define mid ((l+r)>>1)
#define ls (x<<1)
#define rs (x<<1|1)
using namespace std;
int const xn=1e5+;
int n,rt,pre[xn],c[xn][],sta[xn],tp,rev[xn],hd[xn],ct,to[xn<<],nxt[xn<<];
int tim,fa[xn],dfn[xn],sum[xn<<],lzy[xn<<],top[xn],siz[xn],son[xn],dep[xn];
char dc[];
int rd()
{
int ret=,f=; char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=; ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return f?ret:-ret;
}
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
void turn(int x,int s,int len){sum[x]+=s*len; lzy[x]+=s;}//*len
void pushdown(int x,int l,int r)
{
if(!lzy[x])return;
turn(ls,lzy[x],mid-l+); turn(rs,lzy[x],r-mid);
lzy[x]=;
}
void pushup(int x){sum[x]=sum[ls]+sum[rs];}
void update(int x,int l,int r,int L,int R,int s)
{
if(L>R)return;
if(l>=L&&r<=R){turn(x,s,r-l+); return;}
pushdown(x,l,r);
if(mid>=L)update(ls,l,mid,L,R,s);
if(mid<R)update(rs,mid+,r,L,R,s);
pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
if(L>R)return ;
if(l>=L&&r<=R)return sum[x];
pushdown(x,l,r); int ret=;
if(mid>=L)ret+=query(ls,l,mid,L,R);
if(mid<R)ret+=query(rs,mid+,r,L,R);
return ret;
}
void dfs(int x,int ff)
{
fa[x]=pre[x]=ff;//self splay
dep[x]=dep[ff]+; siz[x]=;
for(int i=hd[x],u;i;i=nxt[i])
if((u=to[i])!=ff)
{
dfs(u,x); siz[x]+=siz[u];
if(siz[u]>siz[son[x]])son[x]=u;
}
}
void dfs2(int x)
{
dfn[x]=++tim;
update(,,n,dfn[x],dfn[x],dep[x]);
if(son[x])top[son[x]]=top[x],dfs2(son[x]);
for(int i=hd[x],u;i;i=nxt[i])
if((u=to[i])!=son[x]&&u!=fa[x])top[u]=u,dfs2(u);
}
void torev(int x){rev[x]^=; swap(c[x][],c[x][]);}//
void reverse(int x)
{
if(!rev[x])return;
//swap(c[x][0],c[x][1]);
//rev[c[x][0]]^=1; rev[c[x][1]]^=1;
if(c[x][])torev(c[x][]);
if(c[x][])torev(c[x][]);
rev[x]=;
}
bool isroot(int x){return c[pre[x]][]!=x&&c[pre[x]][]!=x;}
void rotate(int x)
{
int y=pre[x],z=pre[y],d=(c[y][]==x);
if(!isroot(y))c[z][c[z][]==y]=x;
pre[x]=z; pre[y]=x; pre[c[x][!d]]=y;
c[y][d]=c[x][!d]; c[x][!d]=y;
}
void splay(int x)
{
//reverse!
sta[tp=]=x;
for(int i=x;!isroot(i);i=pre[i])sta[++tp]=pre[i];
while(tp)reverse(sta[tp--]);
while(!isroot(x))
{
int y=pre[x],z=pre[y];
if(!isroot(y))
{
if((c[y][]==x)^(c[z][]==y))rotate(x);
else rotate(y);
}
rotate(x);
}
}
int find(int x,int y)//y in xtree
{
//while(top[y]!=top[x])y=fa[top[y]];//?
while(dep[x]<dep[top[y]])
{
y=top[y];
if(fa[y]==x)return y;
y=fa[y];
}
//while(fa[y]!=x)y=fa[y];
//return y;
return son[x];//
}
void change(int x,int v)
{
if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-)
{
int y=find(x,rt);
update(,,n,,dfn[y]-,v);
update(,,n,dfn[y]+siz[y],n,v);
}
else update(,,n,dfn[x],dfn[x]+siz[x]-,v);
}
int findrt(int x)
{
while(c[x][])x=c[x][];
return x;
}
void access(int x)
{
bool fl=;
for(int t=;x;x=pre[x])
{
splay(x);
if(c[x][])change(findrt(c[x][]),);//
c[x][]=t;
if(t)change(findrt(t),-);//
t=x;
}
}
void makeroot(int x)
{
access(x); splay(x); torev(x);//
}
int main()
{
n=rd(); int m=rd();
for(int i=,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x);
rt=; dfs(,); top[]=; dfs2();
for(int i=,x;i<=m;i++)
{
scanf("%s",dc); scanf("%d",&x);
if(dc[]=='L')access(x);
if(dc[]=='C')makeroot(x),rt=x;
if(dc[]=='Q')
{
int ret=,sz;
if(dfn[rt]>dfn[x]&&dfn[rt]<=dfn[x]+siz[x]-)//in subtree
{
int y=find(x,rt); sz=n-siz[y];
ret+=query(,,n,,dfn[y]-);
ret+=query(,,n,dfn[y]+siz[y],n);
}
else if(rt==x)ret=query(,,n,,n),sz=n;//!!!
else ret=query(,,n,dfn[x],dfn[x]+siz[x]-),sz=siz[x];
printf("ret=%d sz=%d\n",ret,sz);
printf("%.7f\n",1.0*ret/sz);
}
}
return ;
}

然后听说线段树会被卡,要写树状数组,于是干脆把那个未知错误的代码扔了;

但是...树状数组维护序列可以单点修改区间查询,维护差分序列可以区间修改单点查询,如何区间修改区间查询?

找题解,学到新知识了——区间修改区间查询的树状数组!

树状数组里放两个数组,一个维护差分序列 \( d[i] \),另一个维护 \( d[i]*i \);

为什么这样?因为首先,要区间修改,只能维护差分数组;

考虑如何从差分数组得到原数组的前缀和 \( s[p] \),就是 \( \sum\limits_{i=1}^{p} \sum\limits_{j=1}^{i} d[j] \)

发现一个 \( d[j] \) 被算了 \( p-j+1 \) 次,也就是 \( (p+1)-j \) 次;

所以只要维护 \( d[i] \) 和 \( d[i]*i \),到时候查询 \( p \) 位置的原序列前缀和,就是 \( (p+1)*\sum\limits_{i=1}^{p} d[i] - \sum\limits_{i=1}^{p}d[i]*i \);

所以这个似乎很好用的样子;

一晚上栽在 splay 之前的一系列 reverse 上,又分不清 i 和 x 了呵呵,然而实际上这题做了一天。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int const xn=1e5+;
int n,m,rt,hd[xn],ct,to[xn<<],nxt[xn<<],sta[xn],top,rev[xn],dep[xn];
int tim,fa[xn][],pre[xn],c[xn][],dfn[xn],ed[xn];
ll s[xn],g[xn];
int rd()
{
int ret=,f=; char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=; ch=getchar();}
while(ch>=''&&ch<='')ret=ret*+ch-'',ch=getchar();
return f?ret:-ret;
}
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
void update(int x,ll v){for(int i=x;i<=n;i+=(i&-i))s[i]+=v,g[i]+=x*v;}//g[i]+=x*v
ll query(int x)
{
if(x>n)return ;
ll s1=,s2=;
for(int i=x;i;i-=(i&-i))s1+=s[i],s2+=g[i];
return (x+)*s1-s2;
}
void dfs(int x,int ff)
{
pre[x]=fa[x][]=ff;
for(int i=;fa[fa[x][i-]][i-];i++)fa[x][i]=fa[fa[x][i-]][i-];
dfn[x]=++tim; dep[x]=dep[ff]+;
for(int i=hd[x],u;i;i=nxt[i])
if((u=to[i])!=ff)dfs(u,x);
ed[x]=tim;
update(dfn[x],); update(ed[x]+,-);
}
bool isroot(int x){return c[pre[x]][]!=x&&c[pre[x]][]!=x;}
void rever(int x)
{
rev[x]^=;
swap(c[x][],c[x][]);
}
void pushdn(int x)
{
if(!rev[x])return;
rever(c[x][]); rever(c[x][]);
rev[x]=;
}
void rotate(int x)
{
int y=pre[x],z=pre[y],d=(c[y][]==x);
if(!isroot(y))c[z][c[z][]==y]=x;
pre[x]=z; pre[y]=x; pre[c[x][!d]]=y;
c[y][d]=c[x][!d]; c[x][!d]=y;
}
void splay(int x)
{
sta[top=]=x;
for(int i=x;!isroot(i);i=pre[i])sta[++top]=pre[i];//i!!!
while(top)pushdn(sta[top--]);
while(!isroot(x))
{
int y=pre[x],z=pre[y];
if(!isroot(y))
((c[y][]==x)^(c[z][]==y))?rotate(x):rotate(y);
rotate(x);
}
}
int find(int x,int y)//y in x's subtree
{
for(int i=;i>=;i--)
if(dep[fa[y][i]]>dep[x])y=fa[y][i];
return y;
}
void change(int x,int v)
{
while(c[x][])pushdn(x),x=c[x][];
if(rt==x)update(,v);
else if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x])
{
int y=find(x,rt);
update(,v); update(dfn[y],-v); update(ed[y]+,v);
}
else update(dfn[x],v),update(ed[x]+,-v);
}
void access(int x)
{
for(int t=;x;x=pre[x])
{
splay(x);
if(c[x][])change(c[x][],);
c[x][]=t;
if(t)change(t,-);
t=x;
}
}
void makeroot(int x)
{
access(x); splay(x); rever(x);
}
double getans(int x)
{
if(rt==x)return 1.0*query(n)/n;
if(dfn[rt]>dfn[x]&&dfn[rt]<=ed[x])
{
int y=find(x,rt);
ll ret=query(n)-query(ed[y])+query(dfn[y]-);
return 1.0*ret/(n-(ed[y]-dfn[y]+));
}
ll ret=query(ed[x])-query(dfn[x]-);
return 1.0*ret/(ed[x]-dfn[x]+);
}
char dc[];
int main()
{
n=rd(); m=rd();
for(int i=,x,y;i<n;i++)x=rd(),y=rd(),add(x,y),add(y,x);
dfs(,);
for(int i=,x;i<=m;i++)
{
scanf("%s",dc); x=rd();
if(dc[]=='L')access(x);
if(dc[]=='C')makeroot(x),rt=x;
if(dc[]=='Q')printf("%.10f\n",getans(x));
}
return ;
}

bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)的更多相关文章

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

    题目描述 给出一棵n个节点的树,每一个节点开始有一个互不相同的颜色,初始根节点为1. 定义一次感染为:将指定的一个节点到根的链上的所有节点染成一种新的颜色,代价为这条链上不同颜色的数目. 现有m次操作 ...

  2. 【bzoj5173】[Jsoi2014]矩形并 扫描线+二维树状数组区间修改区间查询

    题目描述 JYY有N个平面坐标系中的矩形.每一个矩形的底边都平行于X轴,侧边平行于Y轴.第i个矩形的左下角坐标为(Xi,Yi),底边长为Ai,侧边长为Bi.现在JYY打算从这N个矩形中,随机选出两个不 ...

  3. 【bzoj3132】上帝造题的七分钟 二维树状数组区间修改区间查询

    题目描述 “第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵. 第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作. ...

  4. 【bzoj4540】[Hnoi2016]序列 单调栈+离线+扫描线+树状数组区间修改区间查询

    题目描述 给出一个序列,多次询问一个区间的所有子区间最小值之和. 输入 输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数.接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i ...

  5. 1082 线段树练习 3 && 树状数组区间修改区间查询

    1082 线段树练习 3 题意: 给定序列初值, 要求支持区间修改, 区间查询 Solution 用树状数组, 代码量小, 空间占用小 巧用增量数组, 修改时在 \(l\) 处 $ + val$ , ...

  6. [POJ3468]关于整数的简单题 (你想要的)树状数组区间修改区间查询

    #include <cstdio> #include <algorithm> #include <cstring> #include <cctype> ...

  7. 【bzoj3110】[Zjoi2013]K大数查询 整体二分+树状数组区间修改

    题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数 ...

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

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

  9. 【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询

    Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位 ...

随机推荐

  1. 近年现场比赛补题(From 2013 to 2018)[持续更新]

    2013年 Noip提高组 Day1 Day2 2014年 Noip提高组 Day1 Day2 2015年 2016年 2017年 2018年

  2. lastIndexOf 方法 (Array) (JavaScript)

    lastIndexOf 方法 (Array) (JavaScript) 返回指定的值在数组中的最后一个匹配项的索引. 语法         array1.lastIndexOf(searchEleme ...

  3. codeforces781A Andryusha and Colored Balloons

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  4. Listview_简单使用_(Virtual)

    1.代码来自于“C:\Program Files (x86)\Borland\Delphi7\Demos\Virtual Listview” 1.1.是使用 ListView来显示数据 1.2.自己管 ...

  5. scala学习手记33 - 使用trait进行装饰

    在上一节看到了scala的在实例一级的选择性混入就不得不感叹scala在语法上的扩展性.就通过这样一个特性scala简化了很多在java中的编程概念和设计模式. 比如说在java中常用的组合,以及装饰 ...

  6. js 格式化时间日期函数小结3

    function DateUtil(){}/***功能:格式化时间*示例:DateUtil.Format("yyyy/MM/dd","Thu Nov 9 20:30:37 ...

  7. python脚本2_输入2个数比较大小后从小到大升序打印

    #输入2个数,比较大小后,从小到大升序打印 a = input('first: ') b = input('second: ') if a > b: print(b,a) else: print ...

  8. Linux find grep用法示例

    在linux下面工作,有些命令能够大大提高效率.本文就向大家介绍find.grep命令,他哥俩可以算是必会的linux命令,我几乎每天都要用到他们.本文结构如下: find命令 find命令的一般形式 ...

  9. flask学习(二):认识web

    url详解: URL是Uniform Resource Locator 的简写,统一资源定位符 一个URL由以下及几部分组成: scheme://host:port/path/?query-strin ...

  10. 【scala】apply和update

    我们在使用scala的时候经常会用到对象的apply方法和update方法. 虽然我们表面没有察觉,但是实际上两个方法都会遵循相关约定被调用. apply apply方法的约定:用括号传递给变量(对象 ...