BZOJ 3779: 重组病毒

神仙题.

题意:
一开始有n个点,每个点有一独特的颜色,根为1,操作如下:

  • 1.把点到根的路径上的点的颜色变成一个新的颜色.
  • 2.把点到根的路径上的点的颜色变成一个新的颜色,并换根.
  • 3.求 x x x子树中的点到根的路径上的颜色的颜色总数期望.

由于1.2操作的缘故,所以同一颜色的只能为一条链.
如果把一条链看成一个 s p l a y splay splay,那么一个点到根路径上的颜色总数就为access()内循环的次数.
我们把每个点的代价看为 a c c e s s ( ) access() access()内循环的次数(即到根的虚边个数+1)
a c c e s s ( ) access() access()的时候,实边变虚边时,子树的所有点的代价+1;虚边变实边时,子树所有点的代价-1.

我们用树状数组维护代价.(其实用线段树更好理解)
(不会用树状数组做区间加、区间求和的请戳这里中的树状数组解法讲解)

为了方便求值,我们先预处理出dfs序。
但需要注意的是,求值要分类讨论:

  1. 当 x = r o o t , q u e r y ( 1 , n ) 当x=root,query(1,n) x=rootquery(1,n)
  2. 当 x 为 r o o t 的 祖 宗 时 , 需 要 剔 除 r o o t 所 属 的 x 的 子 树 . 当x为root的祖宗时,需要剔除root所属的x的子树. xroot,rootx.
  3. 否 则 , q u e r y ( i n [ x ] , o u t [ x ] ) ( 详 见 d f s ( ) ) 否则,query(in[x],out[x])(详见dfs()) ,query(in[x],out[x])(dfs())

modify时类似,只是modify的一定不是root.

详见代码:

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc tr[x].son[0]
#define rc tr[x].son[1]
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,m,fa[N][19],in[N],out[N],ts,dep[N],root,bin[19];
struct edge{int y,next;}a[N<<1];int len,last[N];
void ins(int x,int y){a[++len]=(edge){y,last[x]};last[x]=len;}
struct node{int f,son[2];bool v;}tr[N];
struct option{char op;int x;}Q[N];
namespace bit{
	ll a[N],b[N];//a维护差分数组的前缀和,b维护差分值乘位置的前缀和 
	int lowbit(int x){return x&-x;}
	void up(int x,int d){for(ll y=1LL*x*d;x<=n;x+=lowbit(x))a[x]+=d,b[x]+=y;}//不写1LL会炸 
	void change(int l,int r,int d){up(l,d);up(r+1,-d);}//l~r增加d 
	ll down(int x)
	{
		ll sum(0);
		for(ll y=x+1;x;x-=lowbit(x))sum+=y*a[x]-b[x];
		return sum;
	}
	ll query(int l,int r){return down(r)-down(l-1);}
}
void dfs(int x)
{
	in[x]=++ts;
	bit::change(in[x],in[x],dep[x]);
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		if(y==fa[x][0])continue;
		tr[y].f=fa[y][0]=x;for(int i=1;fa[y][i-1];i++)fa[y][i]=fa[fa[y][i-1]][i-1];//求祖宗
		dep[y]=dep[x]+1;dfs(y);
	}
	out[x]=ts;
}
int jump(int x,int y)//x单跳y次
{
	for(int i=0;y;i++)if(y&bin[i])y^=bin[i],x=fa[x][i];
	return x;
}
void modify(int x,int d)//把x的关键集合的值,都增加d 
{
	if(!(in[x]<=in[root]&&in[root]<=out[x]))bit::change(in[x],out[x],d);//根不在x的子树内
	else
	{
		x=jump(root,dep[root]-dep[x]-1);//jump(rt,dep[rt]-dep[x]-1)为x的孩子 
		if(in[x]>1) bit::change(1,in[x]-1,d);//逗比了 
		if(out[x]<n)bit::change(out[x]+1,n,d);
	}
}
double ask(int x)
{
	if(x==root)return 1.0*bit::query(1,n)/n;
	else if(!(in[x]<=in[root]&&in[root]<=out[x]))
		return 1.0*bit::query(in[x],out[x])/(out[x]-in[x]+1);
	else
	{
		int y=jump(root,dep[root]-dep[x]-1);
		double s=bit::query(1,in[y]-1);int sz=n-(out[y]-in[y]+1);
		if(out[y]<n)s+=bit::query(out[y]+1,n);
		return s/sz;
	}
}
void fz(int x)
{
	tr[x].v=0;swap(lc,rc);
	tr[lc].v^=1;tr[rc].v^=1;
}
void rotate(int x,int w)
{
	int f=tr[x].f,ff=tr[f].f,r,R;
	r=tr[x].son[w];R=f;tr[R].son[1-w]=r;if(r)tr[r].f=R;
	r=x;R=ff;if(tr[R].son[0]==f)tr[R].son[0]=r;else if(tr[R].son[1]==f)tr[R].son[1]=r;tr[r].f=R;
	r=f;R=x;tr[R].son[w]=r;tr[r].f=R;
}
bool rt(int x){return tr[tr[x].f].son[0]!=x&&tr[tr[x].f].son[1]!=x;}
void wh(int x)
{
	if(!rt(x))wh(tr[x].f);
	if(tr[x].v)fz(x);
}
void splay(int x)
{
	wh(x);
	while(!rt(x))
	{
		int f=tr[x].f;
		if(rt(f))rotate(x,tr[f].son[0]==x);
		else
		{
			int ff=tr[f].f,a=(tr[f].son[0]==x),b=(tr[ff].son[0]==f);
			rotate(a^b?x:f,a);rotate(x,b);
		}
	}
}
int find_root(int x)
{
	if(tr[x].v)fz(x);
	while(lc)
	{
		x=lc;
		if(tr[x].v)fz(x);
	}
	return x;
}
void access(int x)
{
	for(int y=0;x;x=tr[y=x].f)
	{
		splay(x);
		if(rc)modify(find_root(rc),1);
		if(y)modify(find_root(y),-1);
		rc=y;//逗比了 
	}
}
void makeroot(int x)
{
	access(x);splay(x);
	tr[x].v^=1;root=x;
}

void qr(int &x)
{
	char c=g;x=0;
	while(!isdigit(c))c=g;
	while(isdigit(c))x=x*10+c-'0',c=g;
}
int main()
{
	qr(n);qr(m);
	bin[0]=1;for(int i=1;bin[i-1]<=n;i++)bin[i]=bin[i-1]<<1;
	int x,y;char s[11];
	for(int i=1;i<n;i++)
		qr(x),qr(y),ins(x,y),ins(y,x);
	dep[1]=root=1;dfs(1);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",s);qr(Q[i].x);
		switch(s[2]){
			case 'L':Q[i].op=1;break;
			case 'C':Q[i].op=2;break;
			case 'Q':Q[i].op=3;break;
		}
	}
	while(m&&Q[m].op!=3)m--;//后面都是无用功 
	for(int i=1;i<=m;i++)
	{
		x=Q[i].x;
		switch(Q[i].op){
			case 1:access(x);break;
			case 2:makeroot(x);break;
			case 3:printf("%.10lf\n",(ask(x)));break;
		}
	}
	return 0;
}

调试找到的bug:

  1. u p 时 y = 1 L L ∗ x ∗ d 时 未 加 1 L L up时y=1LL * x* d时未加1LL upy=1LLxd1LL
  2. m o d i f y 时 d 写 成 了 1. modify时d写成了1. modifyd1.
  3. a c c e s s 时 忘 记 写 r c = y ; access时忘记写rc=y; accessrc=y;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值