點分治總結


為了避免變量名指代不清的問題,我們先規定一下各變量的含義。

const int N = 10005;
struct edge{int to,next,w;}a[N<<1];//邊集數組
int n,k,head[N],cnt;//n,k不解釋,head[]和cnt是邊集數組的輔助變量 
int root,sum;//當前找到的根,當前遞歸這棵樹的大小 
int vis[N];//某一個點是否被當做根過 
int sz[N];//每個點下面的大小 
int f[N];//每個點為根時的最大子樹大小 
int dep[N];//每個點的深度 
int o[N];//每個點的深度(用於排序) (這個是以poj1741為例,其他題目不一定要用到這個)
int ans;//最終統計的答案 

點分治的核心是找一個點作為根,而找出來的這個點就是我們所說的“重心”。

void getroot(int u,int fa)
{
	sz[u]=1;f[u]=0;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		getroot(v,u);
		sz[u]+=sz[v];f[u]=max(f[u],sz[v]); 
	}
	f[u]=max(f[u],sum-sz[u]);
	if (f[u]<f[root]) root=u;
}

每次找出一個根以后,所有點對就只有兩種可能了:
1、兩個點都在根的某一棵子樹中,即路徑不過根;
2、兩個點在根的不同子樹中,或其中一個點就是根,此時路徑必過根。
對於case1顯然遞歸處理,對於case2在此處計算。
沒什么好說的看代碼吧。

POJ1741 Tree

http://poj.org/problem?id=1741
給出一棵樹,給一個K值,求一共有多少點對滿足\(dis(u,v)<=K\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 10005;
struct edge{int to,next,w;}a[N<<1];
int n,m,k,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],o[N],ans;
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;
}
void getroot(int u,int fa)
{
	sz[u]=1;f[u]=0;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		getroot(v,u);
		sz[u]+=sz[v];
		f[u]=max(f[u],sz[v]);
	}
	f[u]=max(f[u],sum-sz[u]);
	if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
	o[++cnt]=dep[u];
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		dep[v]=dep[u]+a[e].w;getdeep(v,u);
	}
}
int calc(int u,int d0)
{
	cnt=0;dep[u]=d0;
	getdeep(u,0);
	sort(o+1,o+cnt+1);
	int l=1,r=cnt,res=0;
	while (l<r)
		if (o[l]+o[r]<=k) res+=r-l,l++;
		else r--;
	return res;
}
void solve(int u)
{
	ans+=calc(u,0);
	vis[u]=1;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (vis[v]) continue;
		ans-=calc(v,a[e].w);
		sum=sz[v];root=0;
		getroot(v,0);
		solve(root);
	}
}
int main()
{
	while (1)
	{
		n=gi();k=gi();
		if (n==0&&k==0) return 0;
		memset(head,0,sizeof(head));
		memset(vis,0,sizeof(vis));
		cnt=0;ans=0;
		for (int i=1,u,v,w;i<n;i++)
		{
			u=gi();v=gi();w=gi();
			a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
			a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
		}
		root=0;sum=f[0]=n;
		getroot(1,0);
		solve(root);
		printf("%d\n",ans);
	}
}

Luogu3806 【模板】點分治1

https://www.luogu.org/problemnew/show/P3806
這次是詢問距離為k的點對是否存在。
這種詢問就很煩,但是發現了一點:
我們每次把原問題至少分成了兩份\(n/2\)大小的子問題,如果我們每次划分的時候,對當前大小的問題做一個\(O(n^2)\)的操作,那么最終的復雜度回與\(O(n^2)\)同級,但小於\(O(n^2)\)
因為點分治的題一般都是\(n\leq10000\),所以這個復雜度是可!以!過!的!
然后poj1741寫這種復雜度會TLE。。。

2018-10-31upt:
各位觀眾姥爺們不要看上面的強行解釋,也不要看下面這份代碼了。
只要開個桶記錄一下深度就行了,自己一年前真是naive。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 10005;
struct edge{int to,next,w;}a[N<<1];
int n,m,k,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],o[N],ans[10000005];
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;
}
void getroot(int u,int fa)
{
	sz[u]=1;f[u]=0;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		getroot(v,u);
		sz[u]+=sz[v];f[u]=max(f[u],sz[v]); 
	}
	f[u]=max(f[u],sum-sz[u]);
	if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
	o[++cnt]=dep[u];
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		dep[v]=dep[u]+a[e].w;getdeep(v,u);
	}
}
void calc(int u,int d0,int add)
{
	cnt=0;dep[u]=d0;
	getdeep(u,0);
	for (int i=1;i<=cnt;i++)
		for (int j=1;j<=cnt;j++)
			ans[o[i]+o[j]]+=add;
}
void solve(int u)
{
	calc(u,0,1);vis[u]=1;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (vis[v]) continue;
		calc(v,a[e].w,-1);
		sum=sz[v];root=0;
		getroot(v,0);
		solve(root);
	}
}
int main()
{
	n=gi();m=gi();
	for (int i=1,u,v,w;i<n;i++)
	{
		u=gi();v=gi();w=gi();
		a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
		a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
	}
	sum=f[0]=n;
	getroot(1,0);
	solve(root);
	for (int i=1;i<=m;i++)
		k=gi(),puts(ans[k]?"AYE":"NAY");
	return 0;
}

聰聰可可

https://www.luogu.org/problemnew/show/2634
給一棵樹,求這棵樹上任選兩個點(注意可以相同)使得它們距離為3的倍數的概率。
點分治每次求出到當前根距離除以3余0,1,2的點的數量\(t_0t_1t_2\)然后答案就是\(t_0*t_0+2*t_1*t_2\)
概率就是總方案數除以\(n^2\)再約個分。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 20005;
struct edge{int to,next,w;}a[N<<1];
int n,head[N],cnt,root,sum,vis[N],sz[N],f[N],dep[N],t[3],ans;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
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;
}
void getroot(int u,int fa)
{
	sz[u]=1;f[u]=0;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		getroot(v,u);
		sz[u]+=sz[v];f[u]=max(f[u],sz[v]);
	}
	f[u]=max(f[u],sum-sz[u]);
	if (f[u]<f[root]) root=u;
}
void getdeep(int u,int fa)
{
	t[dep[u]%3]++;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (v==fa||vis[v]) continue;
		dep[v]=dep[u]+a[e].w;getdeep(v,u);
	}
}
int calc(int u,int d0)
{
	dep[u]=d0;t[0]=t[1]=t[2]=0;
	getdeep(u,0);
	return t[0]*t[0]+2*t[1]*t[2];
}
void solve(int u)
{
	ans+=calc(u,0);vis[u]=1;
	for (int e=head[u];e;e=a[e].next)
	{
		int v=a[e].to;if (vis[v]) continue;
		ans-=calc(v,a[e].w);
		sum=sz[v];root=0;
		getroot(v,0);
		solve(root);
	}
}
int main()
{
	n=gi();
	for (int i=1;i<n;i++)
	{
		int u=gi(),v=gi(),w=gi();
		a[++cnt]=(edge){v,head[u],w};head[u]=cnt;
		a[++cnt]=(edge){u,head[v],w};head[v]=cnt;
	}
	f[0]=sum=n;
	getroot(1,0);
	solve(root);
	int zsy=gcd(ans,n*n);
	printf("%d/%d\n",ans/zsy,n*n/zsy);
	return 0;
}


免責聲明!

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



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