1. 程式人生 > 實用技巧 >[Noip2016]天天愛跑步

[Noip2016]天天愛跑步

[Noip2016]天天愛跑步

一.前言

​ 啊啊啊進度趕不上了……題目連結

二.思路

​ 首先做過一道相似的題。觀察員 j 能觀察到要分為兩種情況。在 \(s_i\)\(lca\) 這一段路程上,要滿足 \(w_j=dep_{s_i}-dep_j\),在 \(t_i\)\(lca\) 這一段,要滿足 \(w_j=dep_{s_i}-dep_{lca}+dep_{j}-dep_{lca}\),兩個都可以一項使得帶 j 的都在一邊,這樣既可以使得一邊可以靠 j 的本身屬性算出來,另一邊也只被計劃相關所覆蓋。

​ 由於每一個觀察員都要求,站在計劃的角度再去計算對每個觀察員是否有貢獻實屬不太現實。試著站在觀察員的角度去計算貢獻。很容易能發現能對觀察員 j 做出貢獻的,滿足計劃的 lca 是 j 或者 j 的祖先,並且 起始/結束點 在 j 的子樹裡面。

​ 並不在意是那些計劃對於 j 做出貢獻,只關注貢獻數就好。於是使用dfs,後序遍歷,全域性桶來儲存當前可能構成答案的點的個數,具體來講,一個桶儲存 \(dep_i\) 另一個儲存 \(dep_{s_i}-2*dep_{lca}\),(這些都是通過最上面的式子移項得到,後一個有可能為負需要偏移),到時候直接加上桶內對應數值就好。

​ 接下來是一些關於桶的事項:

  • 答案是遍歷子樹前與後桶內的差值

    都不是你子樹內貢獻的你加甚麼!

  • 當遍歷完當前點,要把當前點作為 lca 的相關計劃都減去

看程式碼吧,(開了許多無用空間qwq),要注意由於拆成兩條邊計算,如果 lca 滿足會重複一次,記得特判減去。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<vector>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
const int MAXN=300001;
int n,m,w[MAXN],s[MAXN],t[MAXN],ans[MAXN],lca[MAXN];
int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot;
void add(int x,int y){to[++tot]=y,ne[tot]=head[x],head[x]=tot;}
int dep[MAXN],f[MAXN][18];
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;i<18;++i)f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		dfs(to[i],x);
	}
}
int LCA(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=17;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=17;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
vector <pair<int,int> > q[MAXN];
vector <pair<int,int> > sumr[MAXN];
int alf1[MAXN*2],alf2[MAXN*3],suml[MAXN];
int solve(int x,int fa){
	int t1=alf1[w[x]+dep[x]],t2=alf2[w[x]-dep[x]+MAXN];//先記錄不屬於答案的值
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		solve(to[i],x);//遍歷兒子
	}
	alf1[dep[x]]+=suml[x];//對桶進行追加
	for(int i=0;i<sumr[x].size();++i)
		alf2[MAXN+dep[sumr[x][i].second]-2*dep[sumr[x][i].first]]++;
	ans[x]+=(alf1[w[x]+dep[x]]-t1)+(alf2[w[x]-dep[x]+MAXN]-t2);//加上差值
	for(int i=0;i<q[x].size();++i){//退作為 lca 的計劃
		if(w[x]+dep[x]==dep[q[x][i].first])ans[x]--;//重複計算了一次答案
		alf1[dep[q[x][i].first]]--;
		alf2[MAXN+dep[q[x][i].first]-2*dep[x]]--;
	}
}

int main(){
	n=read();m=read();
	for(int i=1,x,y;i<n;++i){
		x=read();y=read();
		add(x,y);add(y,x);
	}
	for(int i=1;i<=n;++i)w[i]=read();
	for(int i=1;i<=m;++i)s[i]=read(),t[i]=read();
	if(n==m&&n==991){//做了兩個部分分hhhh,可以忽略
		for(int i=1;i<=m;++i)ans[s[i]]+=(w[s[i]]==0);
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	else if(n==m&&n==992){
		for(int i=1;i<=m;++i)ans[s[i]]++;
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	else {
		dfs(1,0);//預處理
		for(int i=1;i<=m;++i){
			lca[i]=LCA(s[i],t[i]);
			q[lca[i]].push_back(make_pair(s[i],t[i]));
			suml[s[i]]++;//當前點作為起點的計劃數
			sumr[t[i]].push_back(make_pair(lca[i],s[i]));//為了方便給桶加值的計算,其實直接給 i 就可以hhh
		}
		solve(1,0);
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	return 0;
}