「BJOI 2018」求和

传送门


problem

给定一棵 n n n 个点的有根数,根为 1 1 1。有 m m m 个询问,每次给出 x , y , k x,y,k x,y,k,求:

∑ u ∈ { x → y } d e p u    k \sum_{u\in\{x\rightarrow y\}}\mathrm{dep}_u^{\;k} u{xy}depuk

其中 u ∈ { x → y } u\in\{x\rightarrow y\} u{xy} 表示 u u u x x x y y y 的路径上,且 d e p 1 = 0 \mathrm{dep}_1=0 dep1=0

数据范围: 1 ≤ n , m ≤ 3 × 1 0 5 1≤n,m≤3\times10^5 1n,m3×105 1 ≤ k ≤ 50 1≤k≤50 1k50


solution

其实看到 k ≤ 50 k\le 50 k50,应该就会想到暴力开 50 50 50 个数据结构分别这 k k k 个东西。

分析一下,我们要支持单点加链求和,而且不带修

但是之前在做另一道题时学到了一种套路,我们可以把单点加、链求和替换成子树加、单点查询

一开始意识模糊写了树状数组,发现跑的有点慢,翻一下别人的代码,发现不带修直接维护前缀和即可。

时间复杂度 O ( n k log ⁡ n ) O(nk\log n) O(nklogn)


code

#include<bits/stdc++.h>
using namespace std;
namespace IO{
	const int Rlen=1<<22|1;
	char buf[Rlen],*p1,*p2;
	inline char gc(){
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>
	inline T Read(){
		char c=gc();T x=0,f=1;
		while(!isdigit(c))  f^=(c=='-'),c=gc();
		while( isdigit(c))  x=(x+(x<<2)<<1)+(c^48),c=gc();
		return f?x:-x;
	}
	inline int in()  {return Read<int>();}
}
using IO::in;
const int N=3e5+5,P=998244353;
int n,m;
int add(int x,int y)  {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y)  {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y)  {return 1ll*x*y>=P?1ll*x*y%P:x*y;}
int power(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=mul(a,a))  if(b&1)  ans=mul(ans,a);
	return ans;
}
int Inv(int x)  {return power(x,P-2);}
int t,first[N],v[N<<1],nxt[N<<1];
void edge(int x,int y)  {nxt[++t]=first[x],first[x]=t,v[t]=y;}
int tot,fa[N],dep[N],sze[N],son[N],top[N],pos[N];
void dfs1(int x){
	sze[x]=1;
	for(int i=first[x];i;i=nxt[i]){
		int to=v[i];
		if(to==fa[x])  continue;
		fa[to]=x,dep[to]=dep[x]+1;
		dfs1(to),sze[x]+=sze[to];
		if(sze[son[x]]<sze[to])  son[x]=to;
	}
}
void dfs2(int x,int tp){
	top[x]=tp,pos[x]=++tot;
	if(son[x])  dfs2(son[x],tp);
	for(int i=first[x];i;i=nxt[i])
		if(v[i]!=son[x]&&v[i]!=fa[x])  dfs2(v[i],v[i]);
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])  swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}
int sum[55][N];
void Add(int &x,int y)  {x=add(x,y);}
void Dec(int &x,int y)  {x=dec(x,y);}
void build(){
	for(int i=1;i<=50;++i){
		for(int j=1;j<=n;++j){
			int val=power(dep[j],i);
			Add(sum[i][pos[j]],val),Dec(sum[i][pos[j]+sze[j]],val);
		}
		for(int j=1;j<=n;++j)  Add(sum[i][j],sum[i][j-1]);
	}
}
int Query(int x,int y,int k){
	int lca=LCA(x,y);
	return dec(add(sum[k][pos[x]],sum[k][pos[y]]),add(sum[k][pos[lca]],fa[lca]?sum[k][pos[fa[lca]]]:0));
}
int main(){
	n=in();
	for(int i=1,x,y;i<n;++i){
		x=in(),y=in(),edge(x,y),edge(y,x);
	}
	dfs1(1),dfs2(1,1),build();
	m=in();
	while(m--){
		int x=in(),y=in(),k=in();
		printf("%d\n",Query(x,y,k));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值