题解 丑国传说 · 收税(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);
	}
}

第一次出公开赛,希望大家玩得开心~

posted @ 2021-08-06 21:58  Maplisky  阅读(206)  评论(0编辑  收藏  举报