NOIP2016 天天爱跑步 LCA+差分+桶

5 篇文章 0 订阅
3 篇文章 0 订阅

传送门

NOIP史上最难一题。。。
部分分出正解系列

测试点1-5:直接模拟即可

测试点6-8:树退化成一条链。我们先从 d e p [ S ] &lt; d e p [ T ] dep[S]&lt;dep[T] dep[S]<dep[T]的情况开始想。 d e p dep dep表示该点到点1的距离。如果路径上有个点满足被观察到的条件,那么 d e p [ i ] − d e p [ S ] = w [ i ] dep[i]-dep[S]=w[i] dep[i]dep[S]=w[i]。移项得到 d e p [ S ] = d e p [ i ] − w [ i ] dep[S]=dep[i]-w[i] dep[S]=dep[i]w[i]。可以发现右边的式子是一个只与 i i i有关的式子,所以如果到了这个点,发现有深度为 d e p [ i ] − w [ i ] dep[i]-w[i] dep[i]w[i]的起点经过这个点,那么这条路径就会被这个点观察到。那 d e p [ S ] &gt; d e p [ T ] dep[S]&gt;dep[T] dep[S]>dep[T]的时候呢?只要列出关系: d e p [ S ] − d e p [ i ] = w [ i ] dep[S]-dep[i]=w[i] dep[S]dep[i]=w[i],移项得到 d e p [ S ] = d e p [ i ] + w [ i ] dep[S]=dep[i]+w[i] dep[S]=dep[i]+w[i],就和之前的情况同理了。具体实现中,可以采用一个桶 A [   ] A[\ ] A[ ],记录当前深度的点是否存在,每次到一个新的点时添加从该点开始的路径,结束时减少在该点结束的路径就行了。

测试点9-12:每条路径的节点都从1开始。显然对于点i,路径满足的条件是经过它并且 d e p [ i ] = w [ i ] dep[i]=w[i] dep[i]=w[i]。这样只需要记录路径经过该点的条数,树上差分一波即可。

测试点13-20:终点为1的部分差不多有点像正解了。测试点6-8提示我们要用桶记录,测试点9-12提示我们要用差分的思想,而且所有的路径都是直的。因此,怎么解决弯的路呢?拆分!把一条路拆成S-LCA,LCA-T,这样就可以变成两条直链进行统计了。
对于S-LCA的部分:对于每个在该路径上的点,若满足 d e p [ S ] − d e p [ i ] = w [ i ] dep[S]-dep[i]=w[i] dep[S]dep[i]=w[i],即 d e p [ S ] = d e p [ i ] − w [ i ] dep[S]=dep[i]-w[i] dep[S]=dep[i]w[i]时,这条路径会被该点观察到。对于LCA-T的部分:同理,当 d e p [ T ] − d e p [ i ] = l e n − w [ i ] dep[T]-dep[i]=len-w[i] dep[T]dep[i]=lenw[i],即 d e p [ i ] − w [ i ] = d e p [ T ] − l e n dep[i]-w[i]=dep[T]-len dep[i]w[i]=dep[T]len时,这条路径会被该点观察到。
具体实现中,我们用一个桶 A [   ] A[\ ] A[ ]记录当前深度的点的数量。每次DFS到点i时,查询A中满足条件的点的数量就行了。
可在更新一个点的答案时,因为还可能有和该点相同深度的点,所以我们会把不在同一子树的点统计进答案。桶也不能清空,因为上面的点还需要用它更新自己的答案。因此,我们先DFS它的子树,完毕之后A的增量就是需要统计的答案。
还有,对于等式 d e p [ i ] − w [ i ] = d e p [ T ] − l e n dep[i]-w[i]=dep[T]-len dep[i]w[i]=dep[T]len,由于在查询时值有可能为负,需要将A数组整体平移MAXN。

复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 300010;
//V1[x] : 以x为LCA的路径的起点的集合
//Spn[x]: 以x为路径起点的路径条数
//V2[x]: 以x为终点的路径的起点集合
//V3[x]: 以x为LCA的路径的终点的集合
int fir[MAXN], nxt[MAXN << 1], to[MAXN << 1], cnt;
int dep[MAXN], fa[MAXN], son[MAXN], size[MAXN], top[MAXN];
int p[MAXN][3];
int w[MAXN], t[MAXN], spn[MAXN];
//int vis[MAXN];
int A[MAXN + MAXN], Ans[MAXN]; //A[i]表示起点深度为i的节点个数

vector <int> v1[MAXN + MAXN], v2[MAXN + MAXN], v3[MAXN + MAXN];

void dfs1(int u, int f, int d){
	dep[u] = d, fa[u] = f, size[u] = 1, son[u] = 0;
	for(int i = fir[u]; i != -1; i = nxt[i]){
		int v = to[i];
		if(v == f) continue;
		dfs1(v, u, d + 1);
		size[u] += size[v];
		if(size[v] > size[son[u]]) son[u] = v;
	}
}

void dfs2(int u, int topf){
	top[u] = topf;
	if(!son[u]) return;
	dfs2(son[u], topf);
	for(int i = fir[u]; i != -1; i = nxt[i]){
		int v = to[i];
		if(v != son[u] && v != fa[u]) dfs2(v, v);
	}
}

int lca(int x, int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y]) swap(x, y);
	return x;
}

inline int read(){
	int k = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){k = k*10 + ch - '0'; ch = getchar();}
	return k * f;
}

inline void add_edge(int a, int b){
	to[cnt] = b;
	nxt[cnt] = fir[a];
	fir[a] = cnt++;
}

void Dfs1(int u){
	int pre = A[dep[u] + w[u] + MAXN];
	for(int i = fir[u]; i != -1; i = nxt[i]){
		int v = to[i];
		if(v != fa[u]) Dfs1(v);
	}
	A[dep[u] + MAXN] += spn[u];
	Ans[u] += A[dep[u] + w[u] + MAXN] - pre;
	for(int i = 0; i < (int)v1[u].size(); i++){
		A[v1[u][i] + MAXN]--; //起点到lca的路径统计完了 
	}
}

void Dfs2(int u){
	int pre = A[dep[u] - w[u] + MAXN];
	for(int i = fir[u]; i != -1; i = nxt[i]){
		int v = to[i];
		if(v != fa[u]) Dfs2(v);
	}
	for(int i = 0; i < (int)v2[u].size(); i++){
		A[v2[u][i] + MAXN]++;
	}
	Ans[u] += A[dep[u] - w[u] + MAXN] - pre;
	for(int i = 0; i < (int)v3[u].size(); i++){
		A[v3[u][i] + MAXN]--;
	}
}

int main(){
	freopen("in.txt", "r", stdin);
	memset(fir, -1, sizeof(fir));
	int n = read(), m = read();
	for(int i = 1; i < n; i++){
		int a = read(), b = read();
		add_edge(a, b);
		add_edge(b, a);
	}
	for(int i = 1; i <= n; i++){
		w[i] = read();
	}
	dfs1(1, 0, 0); dfs2(1, 1); //树链剖分求lca 
	for(int i = 1; i <= m; i++){
		p[i][0] = read(), p[i][1] = read();
		p[i][2] = lca(p[i][0], p[i][1]);
//		printf("u = %d, v = %d, lca = %d\n", p[i][0], p[i][1], p[i][2]);
		spn[p[i][0]]++;
		int d = dep[p[i][0]] + dep[p[i][1]] - 2 * dep[p[i][2]];
		v1[p[i][2]].push_back(dep[p[i][0]]);
		v2[p[i][1]].push_back(dep[p[i][1]] - d);
		v3[p[i][2]].push_back(dep[p[i][1]] - d);
	}
	Dfs1(1); Dfs2(1);

	for(int i = 1; i <= m; i++){
		if(dep[p[i][0]] == dep[p[i][2]] + w[p[i][2]]){ //重复计算了 
			Ans[p[i][2]]--;
		}
	}
	for(int i = 1; i <= n; i++){
		printf("%d ", Ans[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值