1. 程式人生 > 其它 >別人的1024程式設計師節VS你的1024程式設計師節

別人的1024程式設計師節VS你的1024程式設計師節

什麼事長鏈剖分:

  • 對於每個節點令其子樹高度最大的兒子的邊為實邊,其餘邊為虛邊。
  • 於是樹可以剖成由實邊組成的若干長鏈。
  • 同重剖類似,有一個結論:對於任意節點 \(u\),其到根的路徑的上的虛邊數量(長鏈數量)是 \(O(\sqrt n)\) 級別。

CF1009F Dominant Indices

對於每個節點 \(u\),求出一個最小的 \(k\),使得 \(u\) 子樹中到 \(u\) 距離為 \(k\) 的點的數量最大。

對於每個點 \(f(u,d)\) 表示 \(u\) 子樹內到 \(u\) 的距離為 \(d\) 的節點個數。如果直接儲存會炸裂。我們發現其實求 \(u\) 的資訊完全可以直接利用一些在子節點求得的資訊然後再往上增量。這點資訊,不需要 copy,直接用指標即可。

我們考慮對於每一個長鏈申請一個空間。從指標理解,\(f_u\) 表示 \(f_u(d)\) 陣列的頭指標。對於節點 \(u\),設它的實邊兒子為 \(v\),則讓 \(f_v=f_u+1\),即長成這樣:

然後對於每個節點,我們不僅從 \(v\) 上直接得到資訊,還需要合併所有虛邊連向的長鏈。這個普通轉移即可。

對於複雜度,每條長鏈最多被合併一次,而合併一次的複雜度為其鏈長,所以總複雜度為 \(\sum len=O(n)\)。而空間複雜度也一樣,每條鏈所需空間為鏈長,同樣得到 \(O(n)\)

對於分配記憶體,維護一個指標,指向一個數組的位置,然後將長 \(dep+1\) 的一段分配給它。

記憶體分配還是開大一點好。

https://codeforces.com/contest/1009/submission/133302570

const int N=1e6+9;
int n,h[N],son[N],g[N<<1],*f[N],ans[N],*cur=g;
vi e[N];

void dfs1(int u,int fa) {
	for(auto v:e[u]) if(v!=fa) {
		dfs1(v,u);
		if(h[v]>=h[son[u]]) son[u]=v, h[u]=h[v]+1;
	}
}
void dfs2(int u,int fa) {
	if(son[u]) {
		f[son[u]]=f[u]+1, dfs2(son[u],u), ans[u]=ans[son[u]]+1;
	}
	int mh=0;
	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
		f[v]=cur, cur+=h[v]+1;
		dfs2(v,u);
		rep(i,0,h[v]) f[u][i+1]+=f[v][i];
		mh=max(mh,h[v]+1);
	}
	f[u][0]=1;
	rep(i,0,mh)
		if(f[u][i]>f[u][ans[u]]||f[u][i]==f[u][ans[u]]&&i<ans[u]) ans[u]=i;
}

int main() {
	n=read();
	rep(i,2,n) {
		int u=read(), v=read();
		e[u].emplace_back(v), e[v].emplace_back(u);
	}
	dfs1(1,0);
	f[1]=cur, cur+=h[1]+1; dfs2(1,0);
	rep(i,1,n) printf("%d\n",ans[i]);
	return 0;
}

經典題

求樹上長度(路徑點數)恰好為 \(k\) 的路徑數量。

考慮在合併鏈的時候統計一下跨過 \(u\) 的鏈的數量即可。

const int N=3e5+9;
int n,k,ans,son[N],h[N],g[N<<1],*f[N],*cur=g;
vi e[N];

void dfs1(int u,int fa) {
	for(auto v:e[u]) if(v!=fa) {
		dfs1(v,u);
		if(h[v]>=h[son[u]]) son[u]=v;
	} h[u]=h[son[u]]+1;
}
void dfs2(int u,int fa) {
	if(son[u]) {
		f[son[u]]=f[u]+1, dfs2(son[u],u); if(h[u]>=k) ans+=f[u][k];
	} f[u][0]++;
	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
		f[v]=cur, cur+=h[v]+1;
		dfs2(v,u);
		rep(i,0,min(k,h[v])) if(k-i-1<=h[u]) ans+=f[u][k-i-1]*f[v][i];
		rep(i,0,h[v]) f[u][i+1]+=f[v][i];
	}
}
z
signed main() {
	n=read(), k=read();
	rep(i,2,n) {
		int u=read(), v=read();
		e[u].emplace_back(v), e[v].emplace_back(u);
	}
	dfs1(1,0), f[1]=cur, cur+=h[1]+1, dfs2(1,0);
	printf("%lld\n",ans);
	return 0;
}

POI2014 Hotels

求樹上選三點兩兩距離相等的方案數。

\(f(u,i)\) 表示子樹中距離 \(u\)\(i\) 的節點個數。然後考慮一種特殊情況,即三個點 \(x,y,z\) (相互距離為 \(d\) 且總體 \(lca=u\))滿足 \(lca(y,z)\)\(u\) 的距離加上 \(z\)\(u\) 的距離等於 \(d\)(即 \(lca(y,z)\)\(z\) 的鏈跨過 \(u\))。針對這種情況,我們設計狀態 \(g(u,i)\) 表示存在多少這樣的 \(y,z\) 滿足到 \(lca(y,z)\) 距離相等(設之為 \(d\)),且 \(lca(y,z)\)\(u\) 的距離為 \(d-i\)

所以答案為(若 \(u,v\) 同時出現,此時意味著 \(f(u)\) 只是一個字首,在和 \(v\) 合併時統計答案)

\[ans=g(u,0) +\sum_i \sum_{v\in son_u} f(u,i-1)\times g(v,i)+f(u,i-1)\times g(v,i)\\ g(u,i)=\sum g(v,i+1)+f(u,i)\times f(v,i-1) \]

長剖增量 DP 即可。

const int N=4e5+9,mod=1e9+7;
int n,*f[N],*g[N],h[N],p[N],*cur=p,son[N],ans;
vi e[N];

void dfs1(int u,int fa) {
	for(auto v:e[u]) if(v!=fa) {
		dfs1(v,u);
		if(h[v]>=h[son[u]]) son[u]=v;
	}
	h[u]=h[son[u]]+1;
}
void dfs2(int u,int fa) {
	if(son[u]) {
		f[son[u]]=f[u]+1, g[son[u]]=g[u]-1;
		dfs2(son[u],u); 
	} f[u][0]=1; ans+=g[u][0];
	for(auto v:e[u]) if(v!=fa&&v!=son[u]) {
		f[v]=cur, cur+=h[v]*2, g[v]=cur, cur+=h[v]*2;
		dfs2(v,u);
		rep(i,0,h[v]-1) ans+=g[u][i+1]*f[v][i], ans%=mod;
		rep(i,1,h[v]-1) ans+=f[u][i-1]*g[v][i], ans%=mod;
		rep(i,0,h[v]-1) g[u][i+1]+=f[u][i+1]*f[v][i];
		rep(i,1,h[v]-1) g[u][i-1]+=g[v][i]; 
		rep(i,0,h[v]-1) f[u][i+1]+=f[v][i];
	}
}

signed main() {
	n=read();
	rep(i,2,n) {
		int u=read(), v=read();
		e[u].emplace_back(v), e[v].emplace_back(u);
	}
	dfs1(1,0), f[1]=cur, cur+=h[1]*2, g[1]=cur, cur+=h[1]*2, dfs2(1,0);
	printf("%lld\n",ans);
	return 0;
}