题解 丑国传说 · 收税(En:Tax Collection)
题目大意
给定一颗以 \(1\) 为根的带权树,对于每个询问 \(\{x,h\}\),求子树 \(x\) 前 \(h\) 层节点权值的异或和。
算法分析:
其实这道题算法还是蛮多的(毕竟是签到题,当然要水水水水)。
这里介绍一种用树状数组的离线算法。
一些约定:
- 用 \(dis_i\) 表示城市 \(i\) 在树上的深度。
事实上,在对整棵树进行深搜的同时,可以用一个全局的树状数组来实时维护整棵树前 \(h\) 层的节点权值和。具体的,先预处理与 \(x\) 有关的询问。利用差分思想,在处理 \(x\) 的子树前,记录未处理子树时前 \(dis_i+h\) 层异或和,并将点 \(x\) 插入树状数组。在回溯时,针对关于 \(x\) 的不同 \(h\),用前 \(dis_i+h\) 层新的异或和异或未处理时的异或和,就可以得到在 \(x\) 的子树中的异或和。储存到对应下标即可。
时间复杂度为 \(\mathcal{O}(n\log n)\),空间复杂度为 \(\mathcal{O}(n)\)。
code:
#include<bits/stdc++.h>
#define ll long long
#define reg register
#define rint reg int
#define F(i,a,b) for(reg int i=(a);i<=(b);++i)
using namespace std;
bool beginning;
inline int read();
const int N=1e6+5,E=N<<1;
int n,m,a[N];
ll val[N];
int ver[E],net[E],head[N],tot;
inline void add(int x,int y) {
ver[++tot]=y,net[tot]=head[x],head[x]=tot;
}
struct P {
int x,h,id;
bool operator <(const P& a)const {
return x<a.x;
}
} q[N];
int dis[N];
ll c[N],ans[N];
inline void insert(int x,ll y) {
if(!x)c[0]+=y;
else while(x<=n)c[x]^=y,x+=x&-x;
}
inline ll query(int x) {
if(!x)return c[0];
ll sum=0;
while(x)sum^=c[x],x-=x&-x;
return sum;
}
vector<int>f[N];
void dfs(int x,int fath) {
dis[x]=dis[fath]+1;
vector<ll>pos;
for(reg int i=0; i<f[x].size(); ++i) {//记录未处理时的值
int k=f[x][i];
pos.push_back(query(q[k].h+dis[x]));
}
insert(dis[x],val[x]);//插入x
for(reg int i=head[x]; i; i=net[i]) {
int &y=ver[i];
if(y==fath)continue;
dfs(y,x);
}
for(reg int i=0; i<f[x].size(); ++i) {//“差分”
int k=f[x][i];
ans[k]=query(q[k].h+dis[x])^pos[i];
}
}
bool ending;
int main() {
// printf("%.2lfMB\n",1.0*(&ending-&beginning)/1024/1024);
n=read(),m=read();
F(i,1,n)val[i]=read();
F(i,2,n) {
a[i]=read();
add(i,a[i]),add(a[i],i);
}
F(i,1,m)q[i].x=read(),q[i].h=read(),f[q[i].x].push_back(i);//记录询问
dfs(1,0);
F(i,1,m) {
printf("%.3f",1.0*ans[i]/1e3+1e-4);
if(i!=m)putchar('\n');
}
return 0;
}
inline int read() {
reg int x=0;
reg char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
除了树状数组,本题还可以使用主席树,树套树等数据结构维护。
其实还有 \(\mathcal{O}(n)\) 的做法,思路和上面的树状数组基本一致,就不多讲了,下面给出代码。
#include <bits/stdc++.h>
#define reg register
using namespace std;
const int M=1000005;
inline void Rd(int &x) {
x=0;
reg char c=getchar();
while(!isdigit(c))c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
int n,m;
int head[M],tot=0;
struct eg {
int v,nxt;
} E[M];
struct qq {
int k,id;
};
vector<qq>ask[M];
int w[M],sub[M],ans[M],sum[M*2];
void solve(int u,int d) {
for(reg int j=0,sz=ask[u].size(); j<sz; ++j)
ans[ask[u][j].id]^=sum[d+ask[u][j].k+1];
sub[u]=w[u];
for(reg int j=head[u]; j; j=E[j].nxt)
solve(E[j].v,d+1),sub[u]^=sub[E[j].v];
sum[d]^=sub[u];
for(reg int j=0,sz=ask[u].size(); j<sz; ++j)
ans[ask[u][j].id]^=sub[u]^sum[d+ask[u][j].k+1];
}
int main() {
Rd(n),Rd(m);
for(reg int i=1; i<=n; ++i)Rd(w[i]);
for(reg int i=2,fa; i<=n; ++i) {
Rd(fa);
E[++tot]=(eg) {
i,head[fa]
};
head[fa]=tot;
}
for(reg int i=1,u,k; i<=m; ++i) {
Rd(u),Rd(k);
ask[u].push_back((qq) {
k,i
});
}
solve(1,1);
for(reg int i=1; i<=m; ++i) {
printf("%.3f", ans[i]/1000.0);
if(i<m) putchar(10);
}
}
第一次出公开赛,希望大家玩得开心~
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/15110477.html