zoukankan      html  css  js  c++  java
  • Count on a tree II [SP10707]

    【题目描述】

    给定一个(n)个节点的树,每个节点表示一个整数,问(u)(v)的路径上有多少个不同的整数。

    【输入格式】

    第一行有两个整数(n)(m)(n=40000,m=100000))。

    第二行有(n)个整数。第(i)个整数表示第(i)个节点表示的整数。

    在接下来的(n-1)行中,每行包含两个整数(u,v),描述一条边((u,v))。

    在接下来的(m)行中,每一行包含两个整数(u,v),询问(u)(v)的路径上有多少个不同的整数。

    【输出格式】

    对于每个询问,输出结果。

    题解

    树上莫队的模板题

    原先莫队是在线性结构上进行的,现在我们要把它挪到树上

    很容易想到要用dfs序之类的东西来处理

    但是这里dfs序并不方便处理 因为树上一条路径上的点的dfs序不是连续一段的

    我们考虑一下这棵树的欧拉序

    dfs序是节点第一次被经过的时间戳,也就是每个点第一次进入搜索栈的时间戳
    而欧拉序则是每个节点出栈时还要被算一次(也就是dfs回溯时再算一次)

    举个例子

    盗一张洛谷题解的图

    这棵树 dfs序可以是1,2,4,6,7,5,3

    则欧拉序是1,2,4,6,6,7,7,5,5,4,2,3,3,1

    我们把一个点入栈的时间戳记为(dfn[x]),出栈时间戳记为(out[x])(个人习惯,当然也有人是用(st[x])(ed[x]))

    现在树上的一条路径能不能被表示成一段连续区间了呢?

    假设询问的是(x,y)间的路径

    第一种情况 (x)(y)的直接祖先

    (反过来也一样,直接把(x,y)交换一下)

    假设问的是(2 ightarrow 7)的路径吧 我们看一下(dfn[2]sim dfn[7])之间的欧拉序是什么样的:

    是2,4,6,6,7

    但是(6)并不在(2 ightarrow 7)的路径里

    注意到只有(6)出现了两次 出现了两次就说明先加上了它的贡献 然后又删掉了 所以有贡献的实际上只有(2,4,7)

    实现的时候可以记录一下当前区间里某个数(i)有没有出现 如果出现了就删掉 反之就加上

    第二种情况 (x)不是(y)的直接祖先

    假设是(7 ightarrow 3),看一下(out[7]sim dfn[3])之间的欧拉序:

    7,5,5,4,2,3

    (5)出现了两次 所以实际上没产生贡献

    但是路径上应该还有一个节点(1)

    这里我们需要判断一下 如果是第二种情况,即(x,y)的最近公共祖先不是(x) 那么统计答案是要额外加上lca的贡献

    到此就和普通莫队没有差别了 分块大小取(sqrt{n})(其实严格来说是(sqrt{2n}),不过差别不大)

    p.s. 怎么保证一定是(x)(y)的祖先 而不是(y)(x)的祖先?如果(dfn[x]>dfn[y])那么(swap(x,y))即可

    代码

    #include <bits/stdc++.h>
    #define N 200005
    using namespace std;
    
    inline int read() {
    	int x = 0, f = 1; char ch = getchar();
    	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
    	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
    	return x * f;
    }
    
    inline void write(int x) {
    	if (x > 9) write(x / 10);
    	putchar(x % 10 + '0');
    }
    
    int n, m, a[N], srt[N], block;
    int head[N], pre[N<<1], to[N<<1], sz;
    int ans[N], nowl, nowr, nowans, cnt[N];
    bool vis[N];
    
    inline void addedge(int u, int v) {
    	pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
    	pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
    }
    
    int fa[N], son[N], rnk[N], top[N], dfn[N], out[N], tme, siz[N], d[N];
    
    void dfs(int x) {
    	siz[x] = 1; dfn[x] = ++tme; rnk[tme] = x;
    	for (int i = head[x]; i; i = pre[i]) {
    		int y = to[i];
    		if (y == fa[x]) continue;
    		d[y] = d[x] + 1;
    		fa[y] = x;
    		dfs(y);
    		siz[x] += siz[y];
    		if (!son[x] || siz[son[x]] < siz[y]) son[x] = y;
    	}
    	out[x] = ++tme; rnk[tme] = x;
    }
    
    void dfs2(int x, int tp) {
    	top[x] = tp; 
    	if (son[x]) dfs2(son[x], tp);
    	for (int i = head[x]; i; i = pre[i]) {
    		int y = to[i];
    		if (y == fa[x] || y == son[x]) continue;
    		dfs2(y, y);
    	}
    }
    
    inline int LCA(int x, int y) {
    	while (top[x] != top[y]) {
    		if (d[top[x]] < d[top[y]]) swap(x, y);
    		x = fa[top[x]];
    	}
    	if (d[x] > d[y]) swap(x, y);
    	return x;
    }
    
    struct query{
    	int l, r, lca, id;
    	inline friend bool operator < (query x, query y) {
    		if (x.l / block != y.l / block) return x.l < y.l;
    		else return x.r < y.r;
    	}
    } q[N];
    
    inline void del(int c) {
    	if (--cnt[c] == 0) nowans--;
    }
    
    inline void add(int c) {
    	if (++cnt[c] == 1) nowans++;
    }
    
    inline void calc(int x) {
    	if (vis[x]) {
    		del(a[x]);
    		vis[x] = 0;
    	} else {
    		add(a[x]);
    		vis[x] = 1;
    	}
    }
    
    int main() {
    	n = read(), m = read();
    	block = sqrt(n);
    	for (int i = 1; i <= n; i++) a[i] = srt[i] = read();
    	sort(srt + 1, srt + n + 1);
    	int mx = unique(srt + 1, srt + n + 1) - srt - 1;
    	for (int i = 1; i <= n; i++) a[i] = lower_bound(srt + 1, srt + mx + 1, a[i]) - srt;
    	for (int i = 1, u, v; i < n; i++) {
    		u = read(), v = read();
    		addedge(u, v);
    	}
    	dfs(1); dfs2(1, 1);
    	for (int i = 1, u, v; i <= m; i++) {
    		u = read(), v = read();
    		if (dfn[u] > dfn[v]) swap(u, v);
    		int lca = LCA(u, v);
    		if (lca == u) {
    			q[i] = {dfn[u], dfn[v], 0, i};
    		} else {
    			q[i] = {out[u], dfn[v], lca, i};
    		}
    	}
    	sort(q + 1, q + m + 1);
    	nowl = 1, nowr = 0;
    	for (int i = 1; i <= m; i++) {
    		while (nowl < q[i].l) calc(rnk[nowl++]);
    		while (nowl > q[i].l) calc(rnk[--nowl]);
    		while (nowr < q[i].r) calc(rnk[++nowr]);
    		while (nowr > q[i].r) calc(rnk[nowr--]);
    		if (q[i].lca) calc(q[i].lca);
    		ans[q[i].id] = nowans;
    		if (q[i].lca) calc(q[i].lca);
    	}
    	for (int i = 1; i <= m; i++) {
    		write(ans[i]);
    		puts("");
    	}
    	return 0;
    } 
    
  • 相关阅读:
    第二章-2、显示/隐藏/切换
    第二章-1、打开链接
    1、创建自定义原件库
    3、常用小例子
    2、快捷键
    IOS开发中(null)与<null>的处理
    iOS UIPageControl的操作,设置圆点大小,显示图片等
    iOS 支付回调区分支付宝和微信的方法
    iOS 字符串的操作,去掉某一个字符或者替换成其他字符
    iOS 判断当前网络状态
  • 原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM73.html
Copyright © 2011-2022 走看看