長鏈剖分總結


長鏈剖分

長鏈剖分是一種類似\(\mbox{dsu on tree}\)的小\(\mbox{trick}\),可以把維護子樹中只與深度有關的信息做到線性的時間復雜度。

實現方式&復雜度證明

對每個點尋找深度最大的兒子作為重兒子,其余作為輕兒子。由此得到了若干條互不相交的長鏈。
在維護信息的過程中,先\(O(1)\)繼承重兒子的信息,再暴力合並其余輕兒子的信息。
因為每個點僅屬於一條長鏈,且一條長鏈只會在鏈頂位置作為輕兒子暴力合並一次,所以時間復雜度線性。
\(O(1)\)繼承重兒子信息這點上有不同的實現方式,一個巧妙的方法是利用指針實現,具體可以參見代碼。

一些簡單的題目

codeforces1009F
給你一棵樹,定義\(d_{x,i}\)表示\(x\)子樹內和\(x\)距離為\(i\)的節點數,對每個\(x\)求使\(d_{x,i}\)最大的\(i\),如有多個輸出最小的。

裸題,沒什么好講的,看代碼吧。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 1e6+5;
int n,to[N<<1],nxt[N<<1],head[N],cnt;
int len[N],son[N],tmp[N],*f[N],*id=tmp,ans[N];
void link(int u,int v){
	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
}
void dfs(int u,int ff){
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=ff){
			dfs(to[e],u);
			if (len[to[e]]>len[son[u]]) son[u]=to[e];
		}
	len[u]=len[son[u]]+1;
}
void dp(int u,int ff){
	f[u][0]=1;
	if (son[u]) f[son[u]]=f[u]+1,dp(son[u],u),ans[u]=ans[son[u]]+1;
	for (int e=head[u];e;e=nxt[e]){
		int v=to[e];if (v==ff||v==son[u]) continue;
		f[v]=id;id+=len[v];dp(v,u);
		for (int j=1;j<=len[v];++j){
			f[u][j]+=f[v][j-1];
			if ((j<ans[u]&&f[u][j]>=f[u][ans[u]])||(j>ans[u]&&f[u][j]>f[u][ans[u]]))
				ans[u]=j;
		}
	}
	if (f[u][ans[u]]==1) ans[u]=0;
}
int main(){
	n=gi();
	for (int i=1;i<n;++i) link(gi(),gi());
	dfs(1,0);f[1]=id;id+=len[1];
	dp(1,0);
	for (int i=1;i<=n;++i) printf("%d\n",ans[i]);
	return 0;
}

[cogs2652]秘術「天文密葬法」
給你一棵樹,每個點有兩個權值\(a_i,b_i\),你需要找出一條長為\(m\)的路徑,最小化\(\frac{\sum a_i}{\sum b_i}\)

很明顯的分數規划。先二分一個\(mid\),於是原問題轉化為判定性問題:是否存在一條長為\(m\)的路徑,使得\(\sum a_i-mid\sum b_i<0\)
那也就是說我們要找一條長為\(m\)且權值和最小的路徑。這里在\(O(1)\)繼承重兒子的時候需要給整個數組加上一個值,實現方式就是對每個點開個變量表示加了多少。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 2e5+5;
int n,m,a[N],b[N],to[N<<1],nxt[N<<1],head[N],cnt,len[N],son[N];
double val[N],tmp[N],*f[N],*id=tmp,ans=1e18;
void link(int u,int v){
	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
}
void dfs(int u,int ff){
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=ff){
			dfs(to[e],u);
			if (len[to[e]]>len[son[u]]) son[u]=to[e];
		}
	len[u]=len[son[u]]+1;
}
void dp(int u,int ff,double mid){
	val[u]=a[u]-mid*b[u];f[u][0]=0;
	if (son[u]) f[son[u]]=f[u]+1,dp(son[u],u,mid),val[u]+=val[son[u]],f[u][0]-=val[son[u]];
	for (int e=head[u];e;e=nxt[e]){
		int v=to[e];if (v==ff||v==son[u]) continue;
		f[v]=id;id+=len[v];dp(v,u,mid);
		for (int j=0;j<len[v]&&j<m;++j)
			if (m-j-1<len[u]) ans=min(ans,f[v][j]+val[v]+f[u][m-j-1]+val[u]);
		for (int j=0;j<len[v]&&j<m;++j)
			f[u][j+1]=min(f[u][j+1],f[v][j]+val[v]-val[u]+a[u]-mid*b[u]);
	}
	if (m<len[u]) ans=min(ans,f[u][m]+val[u]);
}
int main(){
	freopen("cdcq_b.in","r",stdin);
	freopen("cdcq_b.out","w",stdout);
	n=gi();m=gi()-1;
	for (int i=1;i<=n;++i) a[i]=gi();
	for (int i=1;i<=n;++i) b[i]=gi();
	for (int i=1;i<=n;++i) ans=min(ans,1.0*a[i]/b[i]);
	if (m==-2||!m) return printf("%.2lf\n",ans),0;
	for (int i=1;i<n;++i) link(gi(),gi());
	dfs(1,0);
	double l=0,r=N;
	while (r-l>1e-3){
		double mid=(l+r)/2;
		memset(tmp,0x7f,sizeof(tmp));ans=1e18;
		id=tmp;f[1]=id;id+=len[1];dp(1,0,mid);
		if (ans>=0) l=mid;else r=mid;
	}
	if (l>=200000) puts("-1");
	else printf("%.2lf\n",l);
	return 0;
}

[BZOJ4543][POI2014]Hotel加強版
給你一棵樹,從中選\(3\)個點,兩兩距離相等,求方案數。

考慮暴力\(dp\)。設\(f_{i,j}\)表示\(i\)子樹內與\(i\)距離為\(j\)的點的個數,\(g_{i,j}\)表示\(i\)子樹內,滿足第三個點和\(i\)的距離為\(j\)的點對數目。
這樣每次可以拿\(f_{u,j}\times g_{v,j+1}\)\(g_{u,j+1}\times f_{v,j}\)更新答案,拿\(f_{u,j}\times f_{v,j-1}\)更新\(g_{u,j}\)\(f_{v,j}\)更新\(f_{u,j+1}\)\(g_{v,j}\)更新\(g_{u,j-1}\)
仔細觀察會發現,這個\(g\)的更新是反過來的。
所以\(g\)數組就反着開就行了,為了避免出錯可以多開點空間。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
#define ll long long
const int N = 1e5+5;
int n,to[N<<1],nxt[N<<1],head[N],cnt,len[N],son[N];
ll tmp[N<<2],*f[N],*g[N],*id=tmp,ans;
void link(int u,int v){
	to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
	to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
}
void dfs(int u,int ff){
	for (int e=head[u];e;e=nxt[e])
		if (to[e]!=ff){
			dfs(to[e],u);
			if (len[to[e]]>len[son[u]]) son[u]=to[e];
		}
	len[u]=len[son[u]]+1;
}
void dp(int u,int ff){
	if (son[u]) f[son[u]]=f[u]+1,g[son[u]]=g[u]-1,dp(son[u],u);
	f[u][0]=1;ans+=g[u][0];
	for (int e=head[u];e;e=nxt[e]){
		int v=to[e];if (v==ff||v==son[u]) continue;
		f[v]=id;id+=len[v]<<1;g[v]=id;id+=len[v]<<1;dp(v,u);
		for (int j=0;j<len[v];++j){
			if (j) ans+=f[u][j-1]*g[v][j];
			ans+=g[u][j+1]*f[v][j];
		}
		for (int j=0;j<len[v];++j){
			g[u][j+1]+=f[u][j+1]*f[v][j];
			if (j) g[u][j-1]+=g[v][j];
			f[u][j+1]+=f[v][j];
		}
	}
}
int main(){
	n=gi();
	for (int i=1;i<n;++i) link(gi(),gi());
	dfs(1,0);
	f[1]=id;id+=len[1]<<1;g[1]=id;id+=len[1]<<1;dp(1,0);
	printf("%lld\n",ans);return 0;
}

咕咕咕?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM