【bzoj2599】[IOI2011]Race 点分治

点分治
1、求树的重心
2、计算以当前重心为根的子树的答案
3、去掉以当前重心儿子为根的子树的答案
4、枚举每个儿子,分治

考虑计算过程如何实现
我们不妨记一个ans数组,ans[i]表示使用i条边权值为k的有多少对
每次实现2的时候,权值设为+1
每次实现3的时候,权值设为-1

把子树内所有的dis排序,计算有多少对权值和为k的,两个指针扫一遍就可以了。


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 200100 

using namespace std;

struct yts
{
	int d1,d2;
}st[maxn];

int head[maxn],to[2*maxn],next[2*maxn],len[2*maxn],dis[maxn],dep[maxn];
int size[maxn],ans[maxn],fa[maxn];
bool vis[maxn];
int n,m,num,k,top;

bool cmp(yts x,yts y)
{
	return x.d1<y.d1;
}

void addedge(int x,int y,int z)
{
	num++;to[num]=y;len[num]=z;next[num]=head[x];head[x]=num;
}

void get_root(int x,int Size,int &cg)
{
	bool flag=1;
	size[x]=1;
	for (int p=head[x];p;p=next[p])
	  if (!vis[to[p]] && to[p]!=fa[x])
	  {
	  	fa[to[p]]=x;
	  	get_root(to[p],Size,cg);
	  	size[x]+=size[to[p]];
	  	if (size[to[p]]>Size/2) flag=0;
	  }
	if (Size-size[x]>Size/2) flag=0;
	if (flag) cg=x;
}

void dfs1(int x,int d1,int d2)
{
	dis[x]=d1;dep[x]=d2;
	for (int p=head[x];p;p=next[p])
	  if (!vis[to[p]] && to[p]!=fa[x]) fa[to[p]]=x,dfs1(to[p],d1+len[p],d2+1);
}

void dfs2(int x)
{
	st[++top].d1=dis[x];st[top].d2=dep[x];
	for (int p=head[x];p;p=next[p])
	  if (!vis[to[p]] && to[p]!=fa[x]) dfs2(to[p]);
}

void calc(int x,int f)
{
	top=0;
	dfs2(x);
	sort(st+1,st+top+1,cmp);
	for (int i=1,j=top;i<=j;i++)
	{
		while (j>i && st[i].d1+st[j].d1>k) j--;
		for (int p=j;st[i].d1+st[p].d1==k;p--) ans[st[i].d2+st[p].d2]+=f;
	}
}

void solve(int x,int Size)
{
	int cg;
	fa[x]=0;
	get_root(x,Size,cg);
	size[fa[cg]]=Size-size[cg];
	fa[cg]=0;vis[cg]=1;
	dfs1(cg,0,0);
	calc(cg,1);
	for (int p=head[cg];p;p=next[p]) if (!vis[to[p]]) calc(to[p],-1);
	for (int p=head[cg];p;p=next[p]) if (!vis[to[p]]) solve(to[p],size[to[p]]);
}

int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++;y++;
		addedge(x,y,z);addedge(y,x,z);
	}
	solve(1,n);
	for (int i=1;i<n;i++) if (ans[i]) {printf("%d\n",i);return 0;}
	printf("-1\n");
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值