题目: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. Git常用分支操作

    新建分支 git branch branchname 切换到分支dev git checkout branchname 查看所有的分支信息 git branch -a 查看远程分支信息 git bra ...

  2. centos7下安装ngnix1.8.1

    参考 http://www.linuxidc.com/Linux/2016-09/134907.htm 安装依赖 openssl zlib pcre gcc 下载安装包 [root@localhost ...

  3. Android开发中的logcat工具使用

    http://os.51cto.com/art/200905/126051.htm 用adb直接查看log:    adb logcat 清除之前的log: adb logcat -c 加过滤查看lo ...

  4. Linux常用命令.rpm

    1.安装: rpm -ivh 包全名(查询依赖网址:http://www.rpmfind.net) -i(install):安装 -v(verbose):显示详细信息 -h(hash):显示进度 -- ...

  5. NiceFish的ERROR in AppModule is not an NgModule问题

    大漠老师的angular2新手教程项目,nicefish刚开始下下来运行ng serve --prod --aot 是 出现ERROR in AppModule is not an NgModule ...

  6. mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types () or values ()

    mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types ...

  7. sql 生成随机数 以及不重复随机数

    背景:想在表中随机取10条记录,让取出来的数据不重复(表中必须是有个递增列,且递增从1开始间隔为1).  数据表: CREATE TABLE testable ( id INT  IDENTITY(1 ...

  8. PhantomJS 和Selenium模拟页面js点击

    由于自己不怎么会javascripts,无法找全所有的参数进行模拟提交,所以只能寻求Selenium和PhantpmJS的方式. 先说下ubuntu上怎么安装相应的环境,尤其PhantomJS安装比较 ...

  9. iframe框架

    在一个页面里做一个区域引入另一个页面的方法: <a href="http://www.baidu.com" target="in">百度</a ...

  10. Struts06---通配符的使用

    01.创建对应的login.jsp页面 <%@ page language="java" import="java.util.*" pageEncodin ...