题目: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. Zookeeper初步了解

    Zookeeper初步了解: Zookeeper实现了许多复杂的事情,例如实现了Zookeeper Atomic Broadcasting Protocal来广播状态信息的变化,Fast Paxas ...

  2. mkfs.ext4 磁盘分区

    在linux上格式化一个磁盘分区时,出现如下错误 root@d:~# mkfs.ext4 /dev/sdb1 mke2fs 1.41.12 (11-May-2015) mkfs.ext4: inode ...

  3. 利用war包和Tomcat镜像创建Web镜像

      from hub.c.163.com/library/tomcat:tag #代表以tomcat镜像为基础 MAINTAINER huhuixin 18611551449@163.com #代表所 ...

  4. MySQL安装详解图文版(V5.5 For Windows)

    MySQL在Windows中会得到越来越广泛的应用.故整理MySQL安装详解如下,以备不时之需.安装环境:Windows Server 2003 [32bit NTFS]版本信息:MySQL 5.5. ...

  5. 十二道MR习题 - 3 - 交集并集差集

    题目 有两个文件A和B,两个文件中都有几百万行数字,现在需要找出A文件和B文件中数字集合的交集.并集.以及A对B的差集. 简单说一下思路: 这个问题关键在于key和value的设计.这里我将文件中的数 ...

  6. scala学习手记12 - 字段、方法和构造函数

    在上一节创建了一个scala类,如果没有更多的方法,scala类的定义还可以更简单一些,看一下下面这个CreditCard类的定义: class CreditCard(val number: Int, ...

  7. Author Agreement

    Dear Editor,We the undersigned declare that this manuscript entitled “文章标题” is original, has not bee ...

  8. No toolchains found in the NDK toolchains folder for ABI with prefix

    通过Android Studio 的Sdk Manager安装NDK,安装完之后编译失败,报错信息如下: No toolchains found in the NDK toolchains folde ...

  9. org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [bean.xml]: Invocation of init method failed; nested exception is

    在复制xml文件进行修改的时候,我经常将不小心对原文件进行修改,而导致创建bean出错.报错如下所示: Exception sending context initialized event to l ...

  10. python学习笔记(excel中处理日期格式)

    涉及到处理excel文件中日期格式数据 这里自己整理下 两种方法 代码如下: @classmethod def get_time(cls, table, nrows): testtime = [] f ...