点分治练习题单(动态更新)

传送门

有点难,慢慢做。

1.P2634 [国家集训队]聪聪可可

比板子要简单一点,分治时寻找路径时用桶记录模数为 0,1,2 的个数,再进行 01 背包即可。

统计答案时由于两点可以互换,所以最后答案为 (ansn)×2+n

#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
const int N=2e4+5;
int n,root,mp[N],siz[N],tot,vis[N],t[3],p[3],ans,c[3];
struct node
{
	int to,data;
};
vector<node>a[N];
void findzx(int x,int fa)
{
	mp[x]=0;
	siz[x]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		findzx(a[x][i].to,x);
		mp[x]=max(mp[x],siz[a[x][i].to]);
		siz[x]+=siz[a[x][i].to];
	}
	mp[x]=max(mp[x],tot-siz[x]);
	if(mp[x]<mp[root])root=x;
}
void getdis(int x,int fa,int num)
{
	num%=3;
	p[num]++;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		getdis(a[x][i].to,x,num+a[x][i].data);
	}
}
void clac(int x)
{
	t[0]=c[0]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		p[0]=p[1]=p[2]=0;
		getdis(a[x][i].to,x,a[x][i].data);
		for(int j=0;j<3;j++)for(int k=0;k<3;k++)t[j]+=p[k]*c[((j-k)+3)%3];
		c[0]+=p[0];
		c[1]+=p[1];
		c[2]+=p[2];
	}
	ans+=t[0];
	t[0]=t[1]=t[2]=0;
	c[0]=c[1]=c[2]=0;
}
void divide(int x,int num)
{
	vis[x]=1;
	clac(x);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		if(siz[x]>siz[a[x][i].to])tot=siz[a[x][i].to];
		else tot=num-siz[x];
		root=0;
		findzx(a[x][i].to,x);
		divide(root,tot);
	}
}
int gcd(int x,int y)
{
	if(y==0)return x;
	return gcd(y,x%y);
}
int main()
{
	int n;
	scanf("%d",&n); 
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u].push_back({v,w});
		a[v].push_back({u,w});
	}
	mp[0]=1e9;
	tot=n;
	findzx(1,0);
	divide(root,n);
	ans=(ans-n)*2+n;
	int num=gcd(ans,n*n);
	printf("%d/%d",ans/num,n*n/num);
	return 0;
}

2.P2664 树上游戏

我太蒻了,这题好难……

首先进行点分治,分裂时自己分裂的子树对自己的节点可以利用深搜搜出来,如果一个颜色在此节点到根的链上第一次出现,那么对根的贡献为其子树大小,如果不是第一次出现,那么无贡献。

然后考虑子树对子树的贡献,那么先减去自己子树做的贡献,然后搜索整棵子树,对于自己到根上的所有点的贡献都赋为整棵树的大小减去这棵子树的大小,然后求解即可,最后记得回溯自己子树的贡献。

然后是点分治最重要的东东,清空数组。

对于颜色计数 cnti,在回溯时会自动清空,总贡献是一个变量,直接情况即可,子树大小不需要清空,因为每次搜索时会赋初值 1,最后是数组 ci,代表颜色 i 做出的贡献,感觉不是很好清空,那么再搜索一遍整棵子树,将节点颜色对应的 ci 清空。

复杂度:O(n×log(n))废话

#include<iostream>
#include<vector>
#include<cstdio>
#define int long long
using namespace std;
const int N=1e5+5;
int n,root,mp[N],siz[N],tot,vis[N],ans[N],c[N],cnt[N],sum,nbr,t[N],lim;
vector<int>a[N];
void findzx(int x,int fa)
{
	mp[x]=0;
	siz[x]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa||vis[a[x][i]])continue;
		findzx(a[x][i],x);
		mp[x]=max(mp[x],siz[a[x][i]]);
		siz[x]+=siz[a[x][i]];
	}
	mp[x]=max(mp[x],tot-siz[x]);
	if(mp[x]<mp[root])root=x;
}
void dfs(int x,int fa)
{
	cnt[t[x]]++;
	siz[x]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i]]||a[x][i]==fa)continue;
		dfs(a[x][i],x);
		siz[x]+=siz[a[x][i]];
	}
	if(cnt[t[x]]==1)
	{
		c[t[x]]+=siz[x];
		sum+=siz[x];
	}
	cnt[t[x]]--;
}
void change(int x,int fa,int val)
{
	cnt[t[x]]++;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i]]||a[x][i]==fa)continue;
		change(a[x][i],x,val);
	}
	if(cnt[t[x]]==1)
	{
		c[t[x]]+=val*siz[x];
		sum+=val*siz[x];
	}
	cnt[t[x]]--;
}
void dfs2(int x,int fa)
{
	cnt[t[x]]++;
	if(cnt[t[x]]==1)
	{ 
		sum-=c[t[x]];
		lim++;
	}
	ans[x]+=lim*nbr+sum;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa||vis[a[x][i]])continue;
		dfs2(a[x][i],x);
	}
	if(cnt[t[x]]==1)
	{
		sum+=c[t[x]];
		lim--;
	}
	cnt[t[x]]--;
}
void clea(int x,int fa)
{
	cnt[t[x]]=c[t[x]]=0;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i]]||a[x][i]==fa)continue;
		clea(a[x][i],x);
	}
}
void clac(int x)
{
	dfs(x,0);
	ans[x]+=sum-c[t[x]]+siz[x];
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i]])continue;
		cnt[t[x]]++;
		
		c[t[x]]-=siz[a[x][i]];
		sum-=siz[a[x][i]];
		change(a[x][i],x,-1);
		cnt[t[x]]--;
		nbr=siz[x]-siz[a[x][i]];
		dfs2(a[x][i],x);
		cnt[t[x]]++;
		change(a[x][i],x,1);
		c[t[x]]+=siz[a[x][i]];
		sum+=siz[a[x][i]];
		cnt[t[x]]--;
	}
	sum=lim=0;
	clea(x,0);
}
void divide(int x,int num)
{
	clac(x);
	vis[x]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i]])continue;
		if(siz[x]>siz[a[x][i]])tot=siz[a[x][i]];
		else tot=num-siz[x];
		root=0;
		findzx(a[x][i],x);
		divide(root,tot);
	}
}
signed main()
{
	int n;
	scanf("%lld",&n); 
	for(int i=1;i<=n;i++)scanf("%lld",&t[i]);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%lld%lld",&u,&v);
		a[u].push_back(v);
		a[v].push_back(u);
	}
	mp[0]=1e9;
	tot=n;
	findzx(1,0);
	divide(root,n);
	for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	return 0;
}

P3714 [BJOI2017]树的难题

我太蒻了,又调了一天……

本题如果没有 l,r 的限制,就是一个裸的点分治。

现在我们加上了 l,r 的限制条件,那么我们就需要进行区间处理。

如果在范围内进行 01 背包,很明显有单调性,但是由于有排序等操作,复杂度会上升到 O(n×log22(n)),而我太菜不太会 bfs 优化,那既然复杂度都是 log22(n),为什么不用线段树呢。

对于每一个点,分裂时按照其与子节点的边的颜色进行排序,然后在每次对于一个子树有 k 条边的情况,那么另一条分支应该是之前子树中有 (max(1,lk),rk) 区间中的边所得到答案的最大值。

如果合并时边相同,要减去 c(i,j)

所以要维护两个线段树,一个代表与自己颜色不同,一个代表与自己颜色相同

所以大概步骤是这样的:

1、处理每一棵子树的每个深度所得到答案最大值。

2、对所有子树按颜色进行排序。

3、枚举一棵子树的深度,利用线段树求得另一棵子树合法的长度能得到答案的最大值,如果颜色相等要减去根到子节点的颜色所代表的权值。

4、更新同种颜色的线段树。

5、若这个颜色枚举完了,将同种颜色的线段树归并到不同种颜色线段树上,并清空同种颜色线段树。

然后是点分治的最重要的,清空数组。

为方便清空,在进行排序时,会将同种颜色的按深度排序。

那么子树每个深度的最大答案就可以利用深度进行清空,而线段树归并时同样深度也是最后一个树的深度。

然后其他都不是特别难,除了线段树。

线段树建树来清空肯定会 T,所以在每个节点第一次更新后,维护一个栈或队列记录这个点,最后要清空时依次弹出即可。

时间复杂度:O(n×log22(n))

#include<iostream>
#include<cstdio>
#include<stack>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int N=2e5+5;
int n,m,l,r,t[N],mp[N],siz[N],root;
int vis[N],tot,ans=-2000000000,dep[N],c[N],tmp[N],cnt,nl,nr,k,dis[N];
struct segmentree
{
	int f[4*N];
	stack<int>s; 
	inline int ls(int x)
	{
		return x<<1;
	}
	inline int rs(int x)
	{
		return x<<1|1;
	}
	inline void pushup(int x)
	{
		if(f[x]==-2000000000)s.push(x);
		f[x]=max(f[ls(x)],f[rs(x)]);
	}
	void build(int x,int l,int r)
	{
		f[x]=-2000000000;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(ls(x),l,mid);
		build(rs(x),mid+1,r);
		pushup(x);
	}
	void update(int x,int l,int r)
	{
		if(l==r)
		{
			if(f[x]==-2000000000)s.push(x); 
			f[x]=max(f[x],k);
			return;
		}
		int mid=(l+r)>>1;
		if(mid>=nl)update(ls(x),l,mid);
		else update(rs(x),mid+1,r);
		pushup(x);
	}
	int search(int x,int l,int r)
	{
		if(l>=nl&&r<=nr)return f[x];
		int mid=(l+r)>>1,num=-2000000000;
		if(mid>=nl)num=max(num,search(ls(x),l,mid));
		if(mid<nr)num=max(num,search(rs(x),mid+1,r));
		return num;
	}
	void clear()
	{
		while(!s.empty())
		{
			f[s.top()]=-2000000000;
			s.pop();
		}
	}
}t1,t2;
struct node
{
	int to,data;
};
vector<node>a[N];
int cmp(int x,int y)
{
	if(c[x]==c[y])return dep[x]<dep[y];
	return c[x]<c[y];
}
void findzx(int x,int fa)
{
	mp[x]=0;
	siz[x]=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to]||a[x][i].to==fa)continue;
		findzx(a[x][i].to,x);
		siz[x]+=siz[a[x][i].to];
		mp[x]=max(mp[x],siz[a[x][i].to]);
	}
	mp[x]=max(mp[x],tot-siz[x]);
	if(mp[x]<mp[root])root=x;
}
void dfs(int x,int fa)
{
	int len=a[x].size();
	dep[x]=1;
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to]||a[x][i].to==fa)continue;
		dfs(a[x][i].to,x);
		dep[x]=max(dep[x],dep[a[x][i].to]+1);
	}
}
void getdis(int x,int fa,int num,int deep,int sum)
{
	dis[deep]=max(dis[deep],sum);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		int lim=sum;
		if(a[x][i].data!=num)lim+=t[a[x][i].data];
		getdis(a[x][i].to,x,a[x][i].data,deep+1,lim);
	}
}
void clac(int x)
{
	int len=a[x].size();
	cnt=0;
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		c[a[x][i].to]=a[x][i].data;
		dfs(a[x][i].to,x);
		tmp[++cnt]=a[x][i].to;
	}
	sort(tmp+1,tmp+1+cnt,cmp);
	for(int i=1;i<=cnt;i++)
	{
		dis[1]=t[c[tmp[i]]];
		getdis(tmp[i],x,c[tmp[i]],1,dis[1]);
		for(int j=1;j<=dep[tmp[i]];j++)
		{
			if(j>=l&&j<=r)ans=max(ans,dis[j]);
			nl=l-j,nr=r-j;
			if(nl<=0)nl=1;
			int num1=-2000000000,num2=-2000000000;
			if(nl<=nr)num1=t1.search(1,1,n);
			if(nl<=nr)num2=t2.search(1,1,n);
			ans=max(ans,max(num1+dis[j],num2+dis[j]-t[c[tmp[i]]]));
		}
		for(int j=1;j<=dep[tmp[i]];j++)
		{
			nl=j,k=dis[j];
			t2.update(1,1,n);
			dis[j]=-2000000000;
		}
		if(i!=cnt&&c[tmp[i]]!=c[tmp[i+1]])
		{
			for(int j=1;j<=dep[tmp[i]];j++)
			{
				nl=j,nr=j;
				k=t2.search(1,1,n);
				t1.update(1,1,n);
			}
			t2.clear();
		}
	}
	t1.clear();
	t2.clear();
}
void divide(int x,int num)
{
	vis[x]=1;
	clac(x);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		if(siz[x]>siz[a[x][i].to])tot=siz[a[x][i].to];
		else tot=num-siz[x];
		root=0;
		findzx(a[x][i].to,x);
		divide(root,tot);
	}
}
int main()
{
	//freopen("journey1.in","r",stdin);
	scanf("%d%d%d%d",&n,&m,&l,&r);
	for(int i=1;i<=m;i++)scanf("%d",&t[i]);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w); 
		a[u].push_back({v,w});
		a[v].push_back({u,w});
	}
	tot=n;
	mp[0]=2e9;
	t1.build(1,1,n);
	t2.build(1,1,n);
	findzx(1,0);
	for(int i=1;i<=n;i++)dis[i]=-2000000000;
	divide(root,n);
	printf("%d",ans);
	return 0;
}

P4149 [IOI2011]Race

点分治练习题。

对于每一次分裂进行一次背包,背包同样需要动态维护,然后应该就没有然后了(不会的先去看点分治模板,看懂了这个就会了)。

注意,本题节点从 0 开始,所以写(搬模板代码)时要注意些细节即可。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int N=2e5+5;
const int M=105;
const int T=1e6+5;
struct node
{
	int to,data;
};
vector<node>a[N];
int n,k,t[M],root,vis[N],siz[N],mp[N],tot,dis[N],judge[T],tmp1[N],tmp2[N],cnt,q[N],ans=1e9;
void dfs(int x,int fa)
{
	int len=a[x].size();
	siz[x]=1;
	mp[x]=0;
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to]||a[x][i].to==fa)continue;
		dfs(a[x][i].to,x);
		siz[x]+=siz[a[x][i].to];
		mp[x]=max(mp[x],siz[a[x][i].to]);
	}
	mp[x]=max(mp[x],tot-siz[x]);
	if(mp[x]<mp[root])root=x;
}
void get_dis(int x,int fa,int num,int dep)
{
	if(num>1e6)return;
	tmp1[++cnt]=num;
	tmp2[cnt]=dep;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		get_dis(a[x][i].to,x,num+a[x][i].data,dep+1);
	}
}
void clac(int x)
{
	int len=a[x].size();
	int cnp=0;
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		cnt=0;
		dis[a[x][i].to]=a[x][i].data;
		get_dis(a[x][i].to,x,a[x][i].data,1);
		for(int j=1;j<=cnt;j++)if(k>=tmp1[j])ans=min(ans,judge[k-tmp1[j]]+tmp2[j]);
		for(int j=1;j<=cnt;j++)
		{
			if(judge[tmp1[j]]>=1e8)q[++cnp]=tmp1[j];
			judge[tmp1[j]]=min(judge[tmp1[j]],tmp2[j]);
		}
	}
	for(int i=1;i<=cnp;i++)judge[q[i]]=1e9;
}
void solve(int x,int num)
{
	vis[x]=1;
	clac(x);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		if(siz[x]>siz[a[x][i].to])tot=siz[a[x][i].to];
		else tot=num-siz[x];
		root=n+1;
		dfs(a[x][i].to,-1);
		solve(root,tot);
	}
}
int main()
{
	freopen("P4149_4.in","r",stdin);
	scanf("%d%d",&n,&k);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u].push_back({v,w});
		a[v].push_back({u,w});
	}
	tot=n;
	root=n+1;
	mp[n+1]=1e9;
	memset(judge,0x3f,sizeof(judge));
	judge[0]=0;
	dfs(1,-1);
	solve(root,tot);
	if(ans==1e9)ans=-1;
	printf("%d",ans);
	return 0;
}

P4178 Tree

很明显的点分治。

分治时对于子树上每一个不小于 k 的边长 d,可以与之前小于等于 kd 的边进行贡献,这个就很明显是点分治套数据结构了,这里选择树状数组。

清空树状数组时注意只能一个一个减,不能直接 memset。

其他貌似就与板子一样了,会模板的这个应该都看得懂吧……

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=4e4+5;
int n,k,root,tot,siz[N],mp[N],vis[N],ans,judge[N],tmp[N],q[N],cnt,f[N];
struct node
{
	int to,data;
};
vector<node>a[N];
void findzx(int x,int fa)
{
	siz[x]=1;
	mp[x]=0;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		findzx(a[x][i].to,x);
		siz[x]+=siz[a[x][i].to];
		mp[x]=max(mp[x],siz[a[x][i].to]);
	}
	mp[x]=max(mp[x],tot-siz[x]);
	if(mp[x]<mp[root])root=x;	
}
void getdis(int x,int fa,int num)
{
	if(num>k)return;
	tmp[++cnt]=num;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa||vis[a[x][i].to])continue;
		getdis(a[x][i].to,x,num+a[x][i].data);
	}
}
inline int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int kt)
{
	x++;
	while(x<=k+1)
	{
		f[x]+=kt;
		x+=lowbit(x);
	}
}
int search(int x)
{
	x++;
	int sum=0;
	while(x)
	{
		sum+=f[x];
		x-=lowbit(x);
	}
	return sum;
}
void clac(int x)
{
	int len=a[x].size();
	int cnp=0;
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		cnt=0;
		getdis(a[x][i].to,x,a[x][i].data);
		ans+=cnt;
		for(int i=1;i<=cnt;i++)ans+=search(k-tmp[i]);
		for(int i=1;i<=cnt;i++)update(tmp[i],1),q[++cnp]=tmp[i];
	}
	for(int i=1;i<=cnp;i++)update(q[i],-1);
}
void divide(int x,int num)
{
	vis[x]=1;
	clac(x);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(vis[a[x][i].to])continue;
		if(siz[x]>siz[a[x][i].to])tot=siz[a[x][i].to];
		else tot=num-siz[x];
		root=0;
		findzx(a[x][i].to,0);
		divide(root,tot);
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u].push_back({v,w});
		a[v].push_back({u,w});
	}
	scanf("%d",&k);
	mp[0]=1e9;
	tot=n;
	findzx(1,0);
	divide(1,tot);
	printf("%d",ans);
	return 0;
}
posted @   Gmt丶Fu9ture  阅读(180)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示