[清华集训2017] Hello World!

发布时间 2023-10-30 21:31:31作者: 灰鲭鲨

Hello world!

题目背景

不远的一年前,小 V 还是一名清华集训的选手,坐在机房里为他已如风中残烛的OI 生涯做最后的挣扎。而如今,他已成为了一名光荣的出题人。他感到非常激动,不禁感叹道: “Hello world!”。

题目描述

小 V 有 \(n\) 道题,他的题都非常毒瘤,所以关爱选手的 ufozgg 打算削弱这些题。为了逃避削弱,小 V 把他的毒瘤题都藏到了一棵 \(n\) 个节点的树里(节点编号从 \(1\)\(n\)),这棵树上的所有节点与小 V 的所有题一一对应。小 V 的每一道题都有一个毒瘤值,节点 \(i\) (表示标号为 \(i\) 的树上节点,下同)对应的题的毒瘤值为 \(a_i\)

魔法师小 V 为了保护他的题目,对这棵树施了魔法,这样一来,任何人想要一探这棵树的究竟,都必须在上面做跳跃操作。每一次跳跃操作包含一个起点 \(s\) 、一个终点 \(t\) 和一个步频 \(k\) ,这表示跳跃者会从 \(s\) 出发,在树上沿着简单路径多次跳跃到达 \(t\) ,每次跳跃,如果从当前点到 \(t\) 的最短路长度不超过 \(k\) ,那么跳跃者就会直接跳到 \(t\) ,否则跳跃者就会沿着最短路跳过恰好 \(k\) 条边。

既然小 V 把题藏在了树里, ufozgg 就不能直接削弱题目了。他就必须在树上跳跃,边跳跃边削弱题目。 ufozgg 每次跳跃经过一个节点(包括起点 \(s\) ,当 \(s = t\) 的时候也是如此),就会把该节点上的题目的毒瘤值开根并向下取整:即如果他经过了节点 \(i\),他就会使 \(a_i = \lfloor \sqrt{a_i} \rfloor\)。这种操作我们称为削弱操作。

ufozgg 还会不时地希望知道他对题目的削弱程度。因此,他在一些跳跃操作中会放弃对题目的削弱,转而统计该次跳跃经过节点的题目毒瘤值总和。这种操作我们称为统计操作。

吃瓜群众绿绿对小 V 的毒瘤题和 ufozgg 的削弱计划常感兴趣。他现在想知道ufozgg 每次做统计操作时得到的结果。你能帮帮他吗?

输入格式

从文件 helloworld.in 中读入数据。

输入的第一行一个正整数 \(n\) ,表示树的节点数。

接下来一行 \(n\) 个用空格隔开的正整数 \(a_1, a_2, \ldots, a_n\),依次描述每个节点上题目的毒瘤值。

接下来 \(n − 1\) 行,描述这棵树。每行 \(2\) 个正整数 \(u, v\) ,描述一条树上的边 \((u, v)\) 。(保证 \(1 \leq u, v \leq n\) ,保证这 \(n − 1\) 条边构成了一棵树)

接下来一行一个正整数 \(Q\) ,表示 ufozgg 的操作总数。

接下来 \(Q\) 行按 ufozgg 执行操作的先后顺序依次描述每个操作,每行 \(4\) 个用空格隔开的整数 \(op, s, t, k\) ,表示 ufozgg 此次跳跃的起点为 \(s\) ,终点为 \(t\) ,步频为 \(k\) 。如果 \(op = 0\) ,表示这是一次削弱操作;如果 \(op = 1\) ,表示这是一次统计操作。

输出格式

对于每个统计操作,输出一行一个整数,表示此次统计操作统计到的所有题的毒瘤值总和。

样例 #1

样例输入 #1

5
1 2 3 4 5
1 2
2 3
3 4
2 5
5
1 1 4 1
1 1 4 2
0 1 5 2
1 2 4 5
1 1 5 1

样例输出 #1

10
8 6 5

提示

对于 \(100\%\) 的数据,\(n≤50000\), \(Q≤400000\), \(1\leq ai\leq 10^{13}\)
对于所有的操作保证 \(0\leq op\leq 1\)\(1\leq s,t,k\leq n\)

引入:
维护一个数据结构支持单点修改,链求和。这个是可以不用树剖的。可以维护一个点到根之和,修改时相当于子树加,链求和可以差分。所以用树状数组维护 dfs 序就可以了。

\(k\),步,一个很经典的根号分治的线索。

预处理长剖 \(k\) 级祖先,所有询问都可以在 \(O(\frac{nQ}k)\) 的复杂度以内完成。可以先把 \(t\) 向上跳 \(dis(s,t)\bmod k\) 步,然后 \(s\)\(t\) 一起往上 \(k\) 步的跳。

然后考虑把 \(k\) 比较小的情况维护出来。先看修改,首先一个点开根 \(\log\log a\) 次之后就会变成 1。可以用并查集维护一个点向上跳 \(k\) 步,第一个不为 1 的在哪里。然后我们就可以用引入的内容维护所有的 \(k\) 步,维护 \(dep mod k\) 这条链之和。查询时直接询问链就行了。

如果对 \(B\) 以内的 \(k\) 这样预处理复杂度就是 \(nB\log\log A\log n\)
然后
B 取 \(\sqrt{\frac Q{\log\log a\log n}}\) 时,复杂度取到最优值。但是实测时 B 取 25 最好(常数小?)

#include<bits/stdc++.h>
using namespace std;
const int N=50005,B=25;
typedef long long LL;
int n,fa[B][N],f[N][20],ln[N],son[N],q,in[B][N],out[B][N],top[N],dep[N],lg[N],c;
LL ans,a[N];
vector<int>u[N],d[N],g[N];
vector<LL>tr[B][B];
void upd(int k,int p,int x,LL y)
{
	for(int i=x;i<tr[k][p].size();i+=i&-i)
		tr[k][p][i]+=y;
}
void sou(int x,int y)
{
	f[x][0]=y;
	dep[x]=dep[y]+1;
	for(int i=1;i<B;i++)
	{
		in[i][x]=tr[i][dep[x]%i].size();
		tr[i][dep[x]%i].push_back(0);
	}
	for(int i=1;i<20;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for(int v:g[x])
	{
		if(v==y)
			continue;
		sou(v,x);
		if(ln[v]+1>ln[x])
			ln[x]=ln[v]+1,son[x]=v;
	}
	for(int i=1;i<B;i++)
		out[i][x]=tr[i][dep[x]%i].size();
}
void dfs(int x,int y,int tp)
{
	d[tp].push_back(x);
	if(son[x])
		dfs(son[x],x,tp);
	top[x]=tp;
	for(int v:g[x])
		if(v^y&&v^son[x])
			dfs(v,x,v);
	if(x==tp)
	{
		int y=x;
		for(int i=0;i<=ln[x]&&y;i++)
		{
			u[x].push_back(y);
			y=f[y][0];
		}
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	while(dep[x]>dep[y])
		x=f[x][lg[dep[x]-dep[y]]];
	if(x==y)
		return x;
	for(int i=19;~i;--i)
		if(f[x][i]^f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int ask(int x,int k)
{
	if(!k)
		return x;
	int p=x,q=lg[k];
	assert(dep[x]-k>0);
	x=f[x][lg[k]];
	assert(x);
	k-=1<<lg[k];
	if(dep[x]-dep[top[x]]<=k)
		return u[top[x]][k-dep[x]+dep[top[x]]];
	return d[top[x]][dep[x]-dep[top[x]]-k];
}
int find(int k,int x)
{
	if(fa[k][x]==x)
		return x;
	return fa[k][x]=find(k,fa[k][x]);
}
void upd(int x)
{
	if(a[x]==1)
		return;
	LL t=sqrt(a[x]);
	assert(t*t<=a[x]);
	while((t+1)*(t+1)<a[x])
		++t; 
	for(int i=1;i<B;i++)
	{
		upd(i,dep[x]%i,in[i][x],t-a[x]);
		upd(i,dep[x]%i,out[i][x],a[x]-t);
	}
	a[x]=t;
	if(a[x]==1)
		for(int i=1;i<B&&i<dep[x];i++)
			fa[i][x]=find(i,ask(x,i));
}
LL query(int k,int x)
{
	int d=dep[x]%k,p=in[k][x];
	LL ans=0;
	for(;p;p-=p&-p)
		ans+=tr[k][d][p];
	return ans;
}
int main()
{
	for(int i=2;i<N;i++)
		lg[i]=lg[i>>1]+1;
	for(int i=0;i<B;i++)
	{
		for(int j=0;j<B;j++)
			tr[i][j].push_back(0);
		for(int j=1;j<N;j++)
			fa[i][j]=j;
	}
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lld",a+i);
	for(int i=1,u,v;i<n;i++)
		scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
	sou(1,0);
	dfs(1,0,1);
	for(int i=1;i<B;i++)
		for(int j=1;j<=n;j++)
			if(a[j]==1&&i<dep[j])
				fa[i][j]=find(i,ask(j,i));
	for(int i=1;i<B;i++)
		for(int j=1;j<=n;j++)
			upd(i,dep[j]%i,in[i][j],a[j]),upd(i,dep[j]%i,out[i][j],-a[j]);
	scanf("%d",&q);
	while(q--)
	{
		int op,s,t,k,d;
		scanf("%d%d%d%d",&op,&s,&t,&k);
		d=lca(s,t);
		int g=(dep[s]+dep[t]-2*dep[d])%k;
		if(op)
		{
			ans=a[t]+a[s];
			if(g&&dep[t]-g>=dep[d])
			{
				t=ask(t,g);
				ans+=a[t];
			}
			if(k>=B)
			{
				while(dep[s]-k>=dep[d])
				{
					s=ask(s,k);
					ans+=a[s];
				}
				while(dep[t]-k>=dep[d])
				{
					t=ask(t,k);
					ans+=a[t];
				}
			}
			else
			{
				if(dep[s]-k>=dep[d])
				{
					int u=(dep[d]-dep[s]%k+k)%k;
					if(!u)
						u=k;
					ans+=query(k,ask(s,k));
					if(dep[d]>u)
						ans-=query(k,ask(d,u));
				}
				if(dep[t]-k>=dep[d])
				{
					int u=(dep[d]-dep[t]%k+k)%k;
					if(!u)
						u=k;
					ans+=query(k,ask(t,k));
					if(dep[d]>u)
						ans-=query(k,ask(d,u));
				}
			}
			if(dep[d]%k==dep[s]%k)
				ans-=a[d];
			printf("%lld\n",ans);
			++c;
		}
		else
		{
			if(g&&dep[t]-g>dep[d])
			{
				upd(t);
				t=ask(t,g);
			}
			if(d^t)
				upd(t);
			if(d^s)
				upd(s);
			if(k>=B)
			{
				while(dep[s]-k>dep[d])
				{
					s=ask(s,k);
					upd(s);
				}
				while(dep[t]-k>dep[d])
				{
					t=ask(t,k);
					upd(t);
				}
			}
			else
			{
				while(dep[s]-k>dep[d])
				{
					s=find(k,ask(s,k));
					if(dep[s]>dep[d])
						upd(s);
				}
				while(dep[t]-k>dep[d])
				{
					t=find(k,ask(t,k));
					if(dep[t]>dep[d])
						upd(t);
				}
			}
			if(dep[s]%k==dep[d]%k||t==d)
				upd(d);
		}
	}
}