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

[NOIP2016] 天天愛跑步

前言

看這道題不爽很久了,但一直沒有開它,原因是我不會(我太菜了),看了題解還是寫不來,因為我不會線段樹合併。

然後今天學了dsu on tree這種神奇的科技,成功把它A了,效率吊打線段樹合併。

於是寫篇題解紀念一下。

題目連結

洛谷P1600 天天愛跑步

演算法概述

不帶修改的樹上路徑資訊的維護,很容易想到樹上差分。

我們考慮一條路徑,會對哪些點產生貢獻,也就是題目描述中的一個人會被哪些觀察員觀察到。

假設這條路徑為 \((s,t)\),點為 \(x\),我們分兩部分考慮:

  • 若點 \(x\)\(s\)\(lca(s,t)\)(含)的路徑上,則若滿足 \(w[x]=dep[s]-dep[x]\)

    則會在 \(x\) 上產生 \(1\) 的貢獻。

    將式子變形:\(w[x]+dep[x]=dep[s]\),我們就可以發現一個事情,對於任何一個點 \(p\),這種情況的點數即為所有路徑中起點深度為 \(w[p]+dep[p]\) 的數量。

    我們可以形象化地理解為,有 \(m\) 個人,每個人會給 \(s\)\(lca(s,t)\) 路徑上的每個點發放一個型別為 \(dep[s]\) 的物品,而我們要求的就是對於每個點 \(x\),其上型別為 \(w[x]+dep[x]\) 的物品有多少個。

    再抽象一點,即一條路徑 \(s→lca(s,t)\),會將路徑上每個點處權值為 \(dep[s]\)

    的位置覆蓋一次,最終求每個點 \(x\) 上權值為 \(w[x]+dep[x]\) 的位置被覆蓋了幾次。

    此時,樹上差分已經呼之欲出了。我們在每個點上開一個桶,對於每條路徑 \((s,t)\),在點 \(s\) 讓下標為 \(dep[s]\) 的值\(+1\),在點 \(fa[lca(s,t)]\) 上讓下標為 \(dep[s]\) 的值 \(-1\),其中 \(fa[i]\) 表示 \(i\) 的父親節點,最後每個點的資訊應該是以其為根的子樹和,這部分答案即為該點的桶內下標為 \(w[x]+dep[x]\) 的值。

  • 若點 \(x\)\(lca(s,t)\)(不含)的路徑上,設路徑長度為 \(dis\)

    ,則若滿足 \(dis-(dep[t]-dep[x])=w[x]\),根據 \(dis=dep[s]+dep[t]-2*dep[lca(s,t)]\),可將式子變形為:\(w[x]-dep[x]=dep[s]-2*dep[lca(s,t)]\)

    那麼同樣應用樹上差分,在點 \(t\) 上使下標為 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(+1\),在 \(lca(s,t)\) 上使下標為 \(dep[s]-2*dep[lca(s,t)]\) 的值 \(-1\),最後每個點的資訊統計子樹和,答案即為點上下標為 \(w[x]-dep[x]\) 的值。

我們設第一種情況開的桶為 \(up\),第二種情況開的桶為 \(down\),那麼對於每個點 \(x\),其最終答案即為 \(up[w[x]+dep[x]]+down[w[x]-dep[x]]\)

但問題是,我們在每個點上開兩個桶是顯然不可能的,而且就算空間能夠支援每個點開兩個桶,但我們在統計過程中,合併兩個桶的時間複雜度也直接爆炸。

所以我們只能在全域性開兩個桶。

那麼怎麼維護子樹資訊呢?

不帶修改的子樹資訊維護,正是dsu on tree大顯身手的領域。

所以在每個點上開個 \(vector\),把每個點要維護的資訊放進去,然後dsu on tree,需要統計資訊的時候再把資訊拿出來統計就好了。

每個點維護資訊的時間均攤是 \(\frac{4m}{n}\) 的,每個點最多會被統計 \(O(logn)\) 次,一共有 \(n\) 個點,所以最後的時間複雜度是 \(O(mlogn)\) 的。

參考程式碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=3e5+10;
struct Edge{
	int to,nex;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

int fa[N],dep[N],siz[N];
int son[N],top[N],w[N];
vector<int> info[N][4];
int up[N<<1],BUF[N<<2],*down=&BUF[N<<1];
int ans[N];
int n,m;

void dfs1(int p,int f)
{
	fa[p]=f;
	dep[p]=dep[f]+1;
	siz[p]=1;
	int max_son=0;
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==f)continue;
		dfs1(to,p);
		if(siz[to]>max_son)max_son=siz[to],son[p]=to;
		siz[p]+=siz[to]; 
	}
}

void dfs2(int p,int t)
{
	top[p]=t;
	if(!son[p])return;
	dfs2(son[p],t);
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==son[p])continue;
		dfs2(to,to);
	}
}

inline 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]];
	}
	return dep[x]<dep[y]?x:y; 
}

void calc(int p,int flag,int val)
{
	for(int i=0;i<info[p][0].size();i++)up[info[p][0][i]]+=val;
	for(int i=0;i<info[p][1].size();i++)up[info[p][1][i]]-=val;
	for(int i=0;i<info[p][2].size();i++)down[info[p][2][i]]+=val;
	for(int i=0;i<info[p][3].size();i++)down[info[p][3][i]]-=val;
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==flag)continue;
		calc(to,flag,val);
	}
}

void dfs(int p,int keep)
{
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa[p]||to==son[p])continue;
		dfs(to,0);
	}
	if(son[p])dfs(son[p],1);
	calc(p,son[p],1);
	ans[p]=up[dep[p]+w[p]]+down[w[p]-dep[p]];
	if(!keep)calc(p,0,-1);
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d%d",&n,&m);
	for(int i=1,a,b;i<=n-1;i++)
	{
		scanf("%d%d",&a,&b);
		add_edge(a,b);
		add_edge(b,a);
	}
	for(int i=1;i<=n;i++)scanf("%d",&w[i]);
	
	dfs1(1,0);
	dfs2(1,1);
	
	for(int i=1,s,t;i<=m;i++)
	{
		scanf("%d%d",&s,&t);
		int z=lca(s,t);
		info[s][0].push_back(dep[s]);
		info[fa[z]][1].push_back(dep[s]);
		info[t][2].push_back(dep[s]-2*dep[z]);
		info[z][3].push_back(dep[s]-2*dep[z]);
	}
	
	dfs(1,1);
	
	for(int i=1;i<=n;i++)printf("%d ",ans[i]);
	
	return 0;
}