[BJOI2018]求和

1 篇文章 0 订阅
1 篇文章 0 订阅

[BJOI2018]求和
先用倍增找出查询两点的 L C A LCA LCA,如果 L C A LCA LCA就是它们中的一个,那就直接在链上处理,注意 w h i l e while while它是怎么运行的,不要忘了加上最后LCA的贡献(做题的时候一定要想清楚再写,最好造几组特殊数据手玩)(要不是把题解扒了一篇下来对拍我估计还在 W A WA WA)。如果 L C A LCA LCA不是它们中的一个,那就分别拆成两条链来处理,也注意如何计算 L C A LCA LCA的贡献。注意到 k k k很小,直接暴力计算即可(然而我还非常智障的去问神仙有没有公式来算这个 ⋯ \cdots


l u o g u luogu luogu上除了极限数据都 A C AC AC的代码(即直接暴力计算 k k k

#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const ll Mod=998244353;
const int N=300000+50;
ll ans;
int tot,fir[N<<1],nxt[N<<1],to[N<<1],dep[N],f[25][N],n,m;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
	return cnt*f;
}
inline ll ksm(int a,int b){
	ll res=1LL;
	while(b){if(b&1LL) res=res*(ll)a%Mod;a=a*a%Mod;b>>=1LL;}
	return res%Mod;
}
inline void add(int x,int y){
	nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;f[0][u]=fa;
	for(int i=1;i<=20;++i) f[i][u]=f[i-1][f[i-1][u]];
	for(int i=fir[u];i;i=nxt[i]){
		int v=to[i];if(v==fa) continue;
		dfs(v,u);
	}return;
}
inline int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;--i) if(dep[f[i][x]]>=dep[y]) x=f[i][x];
	if(x==y) return y;
	for(int i=20;i>=0;--i) 
		if(f[i][x]!=f[i][y]) x=f[i][x],y=f[i][y];
	return f[0][x];
}
inline ll work1(int u,int v,int k){
	ll res=0;
	while(u!=v){
		res=res+ksm(dep[u],k);
		if(res>Mod) res-=Mod;
		u=f[0][u];
	}
	res+=ksm(dep[v],k);
	return res%Mod;
}
inline ll work2(int u,int v,int Lca,int k){
	ll res=0;
	while(u!=Lca){
		res=res+ksm(dep[u],k);
		if(res>Mod) res-=Mod;
		u=f[0][u];
	}
	while(v!=Lca){
		res=res+ksm(dep[v],k);
		if(res>Mod) res-=Mod;
		v=f[0][v];
	}
	res=res+ksm(dep[Lca],k);
	if(res>Mod) res-=Mod;
	return res%Mod;
}
inline ll del(int u,int v,int k){
	if(dep[u]<dep[v]) swap(u,v);int Lca=LCA(u,v);
	if(Lca==v) return work1(u,v,k);
	else return work2(u,v,Lca,k);
}
signed main(){
	n=read();int x,y;dep[0]=-1;
	for(int i=1;i<n;++i) {x=read(),y=read();add(x,y);add(y,x);}dfs(1,0);
	m=read();int i,j,k;
	while(m--){
		i=read(),j=read(),k=read();
		ans=del(i,j,k)%Mod;
		printf("%lld\n",ans);
	}
	return 0;
}

加上预处理后通过 h a c k hack hack数据的代码
预处理即预处理深度的 k k k次方之和。
注意到我们预处理的过程,记 v a l [ u ] [ k ] val[u][k] val[u][k] u u u节点深度的 k k k次方,那么考虑计算的过程: l c a lca lca及上面都被算了两次,但因为我们本来就要算一次 l c a lca lca的值,所以最后答案就是 v a l [ u ] [ k ] + v a l [ v ] [ k ] − v a l [ l c a ] [ k ] − v a l [ f a [ l c a ] ] [ k ] val[u][k]+val[v][k]-val[lca][k]-val[fa[lca]][k] val[u][k]+val[v][k]val[lca][k]val[fa[lca]][k]
注意最后取模。

#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const ll Mod=998244353;
const int N=300000+50;
ll ans;
int tot,fir[N<<1],nxt[N<<1],to[N<<1],dep[N],f[25][N],n,m,power[N],val[N][55];
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
	return cnt*f;
}
inline ll ksm(int a,int b){
	ll res=1LL;
	while(b){if(b&1LL) res=res*(ll)a%Mod;a=a*a%Mod;b>>=1LL;}
	return res%Mod;
}
inline void add(int x,int y){nxt[++tot]=fir[x];fir[x]=tot;to[tot]=y;}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;f[0][u]=fa;
	for(int i=1;i<=20;++i) f[i][u]=f[i-1][f[i-1][u]];
	for(int i=1;i<=50;++i) power[i]=power[i-1]*dep[u]%Mod;
	for(int i=1;i<=50;++i) val[u][i]=(power[i]+val[fa][i])%Mod;
	for(int i=fir[u];i;i=nxt[i]){
		int v=to[i];if(v==fa) continue;
		dfs(v,u);
	}return;
}
inline int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;--i) if(dep[f[i][x]]>=dep[y]) x=f[i][x];
	if(x==y) return y;
	for(int i=20;i>=0;--i) 
		if(f[i][x]!=f[i][y]) x=f[i][x],y=f[i][y];
	return f[0][x];
}
signed main(){
	freopen("data.in","r",stdin);
	freopen("judge.out","w",stdout);
	n=read();int x,y;dep[0]=-1;power[0]=1;
	for(int i=1;i<n;++i) {x=read(),y=read();add(x,y);add(y,x);}dfs(1,0);
	m=read();int i,j,k;
	while(m--){
		i=read(),j=read(),k=read();
		int Lca=LCA(i,j);
		int ans=(val[i][k]+val[j][k]+2*Mod-val[Lca][k]-val[f[0][Lca]][k])%Mod;
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值