题目【IN】
目前只有开O2能过,STL的常数太大了,QWQ
题意简述,给你一棵树,有点权与边权,然后有很多询问,每次询问你点权在范围内的点到给点点的距离之和。
解法1:动态点分治
我们使用动态点分治来求取答案。
先构建出点分树,树高肯定是在级别内的,然后对于一个分治点(重心),我们在上面记录三个值:
- 表示该重心的子树点数
- 表示该重心子树中的点到重心的距离和
- 表示该重心的子树中的点到重心在点分树上的父亲的距离和
我们维护这三个值是因为计算答案时,对于子树内的点我们可以直接加上,对于祖先部分的点我们直接加上直接到祖先的距离,而对于其它部分的点,我们将其拆分为两条路来统计,然后再减去多余的。
那么对应的式子为
其中开始,然后不断往点分树的根跳即可。
但是在实际的统计中,我们这样实现:
- 先加上子树到当前枚举的的距离和。
- 然后如果当前的点,那么我们再加上当前点到点的距离乘以子树大小。
- 如果点还有分治树上的父亲,那么我们还要减去当前点子树到其父亲距离和,还有点父亲到点的距离乘以点的子树大小。
下面上一张图来详细的讲解一下为什么如此计算:
我们发现对于当前要求的点,如果我们,那么就直接加上当前子树到的距离和,由于还有父亲,所以我们看,当为父亲时,我们这样操作:
- 先将所有子树内的点走到,代价为
- 然后我们减去原来的的子树内的点多走的距离,就是它们先走到了,又从走回了,所以减去它们到的距离和,还有这条边被它们多走的距离。
那么操作的正确性就非常显然了。
而对于权值在的限制,我们可以利用前缀和的思想,求和的答案相减即可得到,所以用一个或者存下来即可(C++中的)
那么每次询问时即可在点分树上跳父亲统计答案即可,复杂度为,预处理点分树,预处理我们用来,所以为,查询的复杂度变成,所以总的复杂度为,对于的数据再加上大常数,所以要开优化。
代码丑陋QAQ:
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
const int M=5e5+10;
int n,Q,A;
int age[M];
struct ss{
int to,last;ll len;
ss(){}
ss(int a,int b,ll c):to(a),last(b),len(c){}
}g[M<<1];
int head[M],cnt;
void add(int a,int b,ll c){
g[++cnt]=ss(b,head[a],c);head[a]=cnt;
g[++cnt]=ss(a,head[b],c);head[b]=cnt;
}
struct node{
int p;
ll s1,s2,s3;
node(){}
node(int a,ll b,ll c,ll d):p(a),s1(b),s2(c),s3(d){}
bool operator <(const node &a)const{return p<a.p;}
};
vector <node> rec[M];
#define pb push_back
typedef vector<node>::iterator iter;
int pos[M],tim,lgp[M],lg;
ll lcaq[25][M],dis[M];
//rmq求lca来求距离
void dfs_rmq(int a,int b){
lcaq[0][pos[a]=++tim]=dis[a];
for(int i=head[a];i;i=g[i].last){
if(g[i].to==b) continue;
dis[g[i].to]=dis[a]+g[i].len;
dfs_rmq(g[i].to,a);
lcaq[0][++tim]=dis[a];
}
}
void init_rmq(){
lgp[2]=lgp[3]=1;
for(int i=4;i<=tim;i++)lgp[i]=lgp[i>>1]+1;
for(lg=1;(1ll<<lg)<=tim;++lg);
for(int i=1;i<=lg;i++){
for(int j=1;j+(1<<(i-1))<=tim;j++){
lcaq[i][j]=min(lcaq[i-1][j],lcaq[i-1][j+(1<<(i-1))]);
}
}
}
ll dist(int a,int b){
ll now=dis[a]+dis[b];
a=pos[a];b=pos[b];
if(!a||!b) return 0;
if(a>b)swap(a,b);
int k=lgp[b-a+1];
ll lcaa=min(lcaq[k][a],lcaq[k][b-(1<<k)+1]);
return now-(lcaa<<1);
}
int sze[M],f[M],son[M],totsze,root;
bool vis[M];
//找重心
void findroot(int a,int fa){
sze[a]=1;son[a]=0;
for(int i=head[a];i;i=g[i].last){
int v=g[i].to;
if(v==fa||vis[v]) continue;
findroot(v,a);
sze[a]+=sze[v];
if(sze[v]>son[a])son[a]=sze[v];
}
if(totsze-sze[a]>son[a])son[a]=totsze-sze[a];
if(son[a]<son[root])root=a;
}
void getall(int a,int fa,int o){
//求信息
rec[o].pb(node(age[a],1,dist(a,o),dist(a,f[o])));
for(int i=head[a];i;i=g[i].last){
if(g[i].to==fa||vis[g[i].to]) continue;
getall(g[i].to,a,o);
}
}
void find(int a){
//找点分树,并求取信息
vis[a]=1;
getall(a,0,a);
rec[a].pb(node(-1,0,0,0));
sort(rec[a].begin(),rec[a].end());
for(int i=0,sz=rec[a].size()-1;i<sz;i++){
rec[a][i+1].s1+=rec[a][i].s1;
rec[a][i+1].s2+=rec[a][i].s2;
rec[a][i+1].s3+=rec[a][i].s3;
}//前缀和
for(int i=head[a];i;i=g[i].last){
if(vis[g[i].to]) continue;
root=0;totsze=sze[g[i].to];
findroot(g[i].to,0);
f[root]=a;
find(root);
}
}
node calc(int o,int l,int r){
//以前缀和的形式保存:s1子树大小,s2子树到根,s3子树到根的分治父亲
if(!o) return node(0,0,0,0);
iter a=upper_bound(rec[o].begin(),rec[o].end(),node(r,0,0,0));--a;
iter b=upper_bound(rec[o].begin(),rec[o].end(),node(l-1,0,0,0));--b;
ll s1=(a->s1)-(b->s1),s2=(a->s2)-(b->s2),s3=(a->s3)-(b->s3);
return node(0,s1,s2,s3);
}
ll getans(int o,int l,int r){
ll ans=0;
for(int a=o;a;a=f[a]){
node now=calc(a,l,r);
ans+=now.s2;//首先由子树到当前的根的贡献
if(a!=o) ans+=now.s1*dist(a,o);//当前的点不是最开始询问点则需要加上子树到当前点a的多余贡献
if(f[a]) ans-=now.s3+now.s1*dist(f[a],o);//如果有分治父亲,那么需要减去子树到根父亲和根到父亲的贡献。
//因为到当前这个点的距离可以分成三种,一种是子树内的直接到,一种是祖先部分的也是直接到,一种为另外一子树内的点
//需要分成两部分,u->lca->v,所以这样就能统计出所有的答案。
}
return ans;
}
ll zans;
ll a,b,c;
int main(){
scanf("%d%d%d",&n,&Q,&A);
for(int i=1;i<=n;i++)scanf("%d",&age[i]);
for(int i=1;i<n;i++){
scanf("%lld%lld%lld",&a,&b,&c);
add(a,b,c);
}
dfs_rmq(1,0);
init_rmq();
root=0;son[0]=M;totsze=n;
findroot(1,0);
find(root);
while(Q--){
scanf("%lld%lld%lld",&a,&b,&c);
b=(1ll*b+zans)%A;c=(1ll*c+zans)%A;
if(b>c)swap(b,c);
zans=getans(a,b,c);
cout<<zans<<'
';
}
return 0;
}
- 动态点分治类似题目【IN-Luogu】
解法2:主席树+树剖
我们可以发现,一个询问点的答案为如下式子:
其中表示到根节点的距离。
这个式子和动态点分治中维护的信息是十分类似的,我们开始先加上所有点到根的距离,然后对于一部分点,它必须走这条路,所以还有根节点回来的路径为,但是这样无疑会多算一些边,所以我们利用差分的思想,一条路影响是不会超过的上方,所以我们减去到根的距离,由于一条路是两个点,所以减两次,那么便是答案了。
对于信息的维护,我们用线段树+树链剖分即可,但是有点权的限制,所以我们用主席树即可做到维护,的查询。
但是此题空间限制较小(对于主席树来说),所以我们使用标记永久化,减少新的节点的开销。
下面上代码的连接我没有打主席树的代码,所以借用的他人的
【IN-Luogu】
复杂度,常数较为小一点(因为没有使用过多的)。