bzoj 3779 重组病毒 —— LCT+树状数组(区间修改+区间查询)
题目: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+树状数组(区间修改+区间查询)的更多相关文章
- 【bzoj3779】重组病毒 LCT+树上倍增+DFS序+树状数组区间修改区间查询
题目描述 给出一棵n个节点的树,每一个节点开始有一个互不相同的颜色,初始根节点为1. 定义一次感染为:将指定的一个节点到根的链上的所有节点染成一种新的颜色,代价为这条链上不同颜色的数目. 现有m次操作 ...
- 【bzoj5173】[Jsoi2014]矩形并 扫描线+二维树状数组区间修改区间查询
题目描述 JYY有N个平面坐标系中的矩形.每一个矩形的底边都平行于X轴,侧边平行于Y轴.第i个矩形的左下角坐标为(Xi,Yi),底边长为Ai,侧边长为Bi.现在JYY打算从这N个矩形中,随机选出两个不 ...
- 【bzoj3132】上帝造题的七分钟 二维树状数组区间修改区间查询
题目描述 “第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵. 第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作. ...
- 【bzoj4540】[Hnoi2016]序列 单调栈+离线+扫描线+树状数组区间修改区间查询
题目描述 给出一个序列,多次询问一个区间的所有子区间最小值之和. 输入 输入文件的第一行包含两个整数n和q,分别代表序列长度和询问数.接下来一行,包含n个整数,以空格隔开,第i个整数为ai,即序列第i ...
- 1082 线段树练习 3 && 树状数组区间修改区间查询
1082 线段树练习 3 题意: 给定序列初值, 要求支持区间修改, 区间查询 Solution 用树状数组, 代码量小, 空间占用小 巧用增量数组, 修改时在 \(l\) 处 $ + val$ , ...
- [POJ3468]关于整数的简单题 (你想要的)树状数组区间修改区间查询
#include <cstdio> #include <algorithm> #include <cstring> #include <cctype> ...
- 【bzoj3110】[Zjoi2013]K大数查询 整体二分+树状数组区间修改
题目描述 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数 ...
- bzoj 3779 重组病毒——LCT维护子树信息
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3779 调了很久……已经懒得写题解了.https://www.cnblogs.com/Zinn ...
- 【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询
Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位 ...
随机推荐
- Mysql导出数据结构 or 数据
如果我们单单只想导出mysql数据表结构,通过navcat工具还不行,这时我们可以用mysqldump工具 在mysql server的安装目录:C:\Program Files\MySQL\MySQ ...
- Android -- UI布局管理,相对布局,线性布局,表格布局,绝对布局,帧布局
1. 相对布局 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmln ...
- QTabWidget和QtabBar的区别
切换标签tab 是QTabBar int QTabWidget::insertTab ( int index, QWidget * page, const QString & label ) ...
- 用纯css写一个常见的小三角形
.test{ margin:50px auto; width: 0; height: 0; overflow: hidden; border-width: 10px; border-color: #0 ...
- SPOJ - LCS2
后缀自动机板子题 https://vjudge.net/problem/28017/origin 找多串的最长公共子串 //#pragma comment(linker, "/stack:2 ...
- saltstack学习篇
参考链接:http://sofar.blog.51cto.com/353572/1596960/ http://sofar.blog.51cto.com/353572/1596960/ 自动化运维工具 ...
- memcache笔记
服务端: 通过printf配合nc向memcached中写入数据[root@yz6245 ~]# printf "set key1 0 0 6\r\noldboy\r\n" |nc ...
- QT延时方法
(转自:http://blog.sina.com.cn/s/blog_613cfe940100kacm.html) 1. void sleep(unsigned int msec){ QTime ...
- sql日期函数总结
sql 时间转换格式 convert(varchar(10),字段名,转换格式) convert(varchar(10),字段名,转换格式) CONVERT(nvarchar(10),count_ ...
- 不安装APK直接启动应用
相信这样一个问题,大家都不会陌生, “有什么的方法可以使Android的程序APK不用安装,而能够直接启动”. 发现最后的结局都是不能实现这个美好的愿望,而腾讯Android手机游戏平台却又能实现这个 ...