题解 DTOJ #3298. 诹访清水(suwako)

欢迎访问 My Luogu Space

【题目大意】

有一棵树, n n n 个节点, 1 1 1 号节点为根。我们定义浇水,每往一个点浇水,以这个点为根的整颗子树上的点都会被浇过水。

现在你有一个序列 A A A,这个序列包含的数字就是节点的编号。每次询问给出一组 l , r l,r l,r,你会将 A A A 序列中 [ l , r ] [l,r] [l,r] 的节点都浇上水,请你求出有多少个叶子节点被浇过了水。


【题解】

莫队

由于本题给出了一个随机的区间,我们考虑用莫队来处理。

主要是线段树方面的问题:

我们先将整颗数dfs求出dfs序,然后以dfs序为下标建立线段树。

线段树的值是被浇过水的叶子数,线段树的标记是被浇了几次的水。

考虑更新操作,如果一个线段树的节点代表的区间的所有节点都被浇过了水,那么这个线段树的节点存的值就是它所代表的区间的叶子数;否则,它的值就是它的左儿子的值加上右儿子的值。

并且由于一个子树是不能被分成多个子树的(因为它有一个根节点),也就是说一个线段树修改的区间是不会其他修改的区间组合而成的,而且一定要先被浇过水才能取消浇水。所以我们的标记不需要下传。(讲不清楚的话自己体会了)。

这样搞就可以了。

(不知道为什么这题的线段树要开不止四倍空间,不然会WA)


【代码】

// output format !!!
// long long !!!
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int MAXN = 100000+10;
struct Q{
	int l, r, id, b;
	bool operator<(Q a)const{
		return b==a.b ? r<a.r : b<a.b;
	}
}q[MAXN];

int n, m, qn, tot;
int t[MAXN*8], A[MAXN], siz[MAXN], dfn[MAXN], sum[MAXN], tag[MAXN*8], ans[MAXN];
int ct, e[MAXN*2], nex[MAXN*2], hed[MAXN];

void dfs(int x, int fa){
	dfn[x] = ++tot;
	siz[dfn[x]] = sum[dfn[x]] = 1;
	for(int i=hed[x]; i; i=nex[i]){
		if(e[i] == fa) continue;
		dfs(e[i], x);
		siz[dfn[x]] += siz[dfn[e[i]]];
		sum[dfn[x]] = 0;
	}
}
#define ls (x<<1)
#define rs (x<<1|1)
void update(int x, int l, int r){
	if(tag[x]) t[x] = sum[r]-sum[l-1];
	else t[x] = t[ls]+t[rs];
}
void modify(int x, int l, int r, int ql, int qr, int v){
	if(ql<=l && r<=qr){
		tag[x] += v;
		return update(x, l, r);
	}
	int mid = (l+r)>>1;
	if(ql <= mid) modify(ls, l, mid, ql, qr, v);
	if(qr > mid) modify(rs, mid+1, r, ql, qr, v);
	update(x, l, r);
}
int main(){
	scanf("%d%d%d", &n, &m, &qn);
	for(int i=1; i<n; ++i){
		int a, b; scanf("%d%d", &a, &b);
		e[++ct]=b, nex[ct]=hed[a], hed[a]=ct;
		e[++ct]=a, nex[ct]=hed[b], hed[b]=ct;
	}
	dfs(1, 1);
	for(int i=1; i<=n; ++i) sum[i] += sum[i-1];
	for(int i=1; i<=m; ++i) scanf("%d", A+i);
	int b = sqrt(m);
	for(int i=1; i<=qn; ++i)
		scanf("%d%d", &q[i].l, &q[i].r), q[i].b=(q[i].l-1)/b+1, q[i].id=i;
	sort(q+1, q+qn+1);
	for(int i=1,L=1,R=0; i<=qn; ++i){
		while(L < q[i].l) modify(1, 1, n, dfn[A[L]], dfn[A[L]]+siz[dfn[A[L]]]-1,-1), ++L;
		while(L > q[i].l) --L, modify(1, 1, n, dfn[A[L]], dfn[A[L]]+siz[dfn[A[L]]]-1, 1);
		while(R < q[i].r) ++R, modify(1, 1, n, dfn[A[R]], dfn[A[R]]+siz[dfn[A[R]]]-1, 1);
		while(R > q[i].r) modify(1, 1, n, dfn[A[R]], dfn[A[R]]+siz[dfn[A[R]]]-1,-1), --R;
		ans[q[i].id] = t[1];
	}
	for(int i=1; i<=qn; ++i) printf("%d\n", ans[i]);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值