点分治 -Race [IOI2011]

传送门


Analysis

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

考虑计算过程如何实现
我们不妨记一个cnt数组,cnt[i]表示使用i条边权值为k的有多少对
每次实现2的时候,权值设为+1
每次实现3的时候,权值设为-1
把子树内所有的dis排序,计算有多少对权值和为k的,两个指针扫一遍就可以了。


Code
#include<bits/stdc++.h>
#define in read()
#define re register
using namespace std;
inline int read(){
	char ch;int f=1,res=0;
	while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
	while(ch>='0'&&ch<='9'){
		res=(res<<1)+(res<<3)+(ch^48);
		ch=getchar();
	}
	return f==1?res:-res;
}
const int N=2e5+10;
int n,K;
int nxt[N<<1],head[N],to[N<<1],w[N<<1],ecnt=0;
inline void add(int x,int y,int z){
	nxt[++ecnt]=head[x];head[x]=ecnt;to[ecnt]=y;w[ecnt]=z;
}
int sze[N],son[N];
bool vis[N];
int G,minn;
int maxn,cnt[2000009],res=N,ans=0;//ans-->长度为k的点对个数,res-->最少边 
struct node{	int dis,dep; }a[N];
inline bool cmp(const node &a,const node &b){
	return a.dis<b.dis;
}
int d[N],dep[N],num;
inline void dfs(int u,int fu){
	a[++num].dis=d[u];a[num].dep=dep[u];
	for(re int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]||v==fu) continue;
		d[v]=d[u]+w[e];dep[v]=dep[u]+1;
		dfs(v,u);
	}
}
inline int calc(int u,int val,int h,int flag){
	num=0;int t=0;
	d[u]=val;dep[u]=h;
	dfs(u,0);
	sort(a+1,a+num+1,cmp);
	for (int i=1,j=num;i<=j;i++)
	{
		while (j>i && a[i].dis+a[j].dis>K) j--;
		for (int p=j;a[i].dis+a[p].dis==K;p--) cnt[a[i].dep+a[p].dep]+=flag;
	}
	return t;
}
inline void dfssize(int u,int fu){
	sze[u]=1;
	for(re int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]||v==fu) continue;
		dfssize(v,u);
		sze[u]+=sze[v];
	}
}
inline int getG(int u,int fu,int size){
	for(re int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]||v==fu) continue;
		if(sze[v]>size) return getG(v,u,size);
	}
	return u;
}
inline void solve(int u){
	minn=n;ans=0;
	dfssize(u,0);
	G=getG(u,0,sze[u]/2);
	vis[G]=1;
	ans+=calc(G,0,0,1);
	for(re int e=head[G];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]) continue;
		ans-=calc(v,w[e],1,-1);
		solve(v);
	}
}
int main(){
	n=in;K=in;
	for(re int i=1;i<n;++i){
		int x=in,y=in,z=in;x++;y++;
		add(x,y,z);add(y,x,z);
	}
	solve(1);
	res=-1;
	for(re int i=0;i<=n;++i) if(cnt[i]) {res=i;break;	}
	printf("%d",res);
	return 0;	
}


吐槽

1.自己以前求重心的方法被卡爆.
一开始是这样的

void dfssize(int u,int fu){
	sze[u]=1;
	for(re int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]||v==fu) continue;
		dfssize(v,u);
		sze[u]+=sze[v];
		if(sze[v]>son[u]) son[u]=sze[v];
	}
}
void getG(int rt,int u,int fu){
	if(sze[rt]-sze[u]>son[u]) son[u]=sze[rt]-sze[u];
	if(son[u]<minn) minn=son[u],G=u;
	for(re int e=head[u];e;e=nxt[e]){
		int v=to[e];
		if(vis[v]||v==fu) continue;
		getG(rt,v,u);
	}
}

(如果有这样写的盆友,一定要改鸭!!!
虽然我到现在也不知道错在哪里(=@__@=))

正确的打开方式:

void dfsSize(int u, int fa) {
    size[u] = 1;
    for (int i = 0, v; i < (int)g[u].size(); i++)
        if (!vis[v = g[u][i]] && v != fa) dfsSize(v, u), size[u] += size[v];
}
int getG(int u, int fa, int n) {
    for (int i = 0, v; i < (int)g[u].size(); i++)
        if (!vis[v = g[u][i]] && v != fa && size[v] > n) return getG(v, u, n);
    return u;
}

补充
xehoth学长说点分治TLE的情况只有几种:
I 重心求错
[网上很多求重心的方法都可能死循环或者求错,但也可以水过一些题。所以,选好模板很重要/笑。]
II calc的时候写成了n2

2.用双指针扫描的时候漏了情况
一开始是这样写的

	int l=1,r=num;
	while(l<r){
		if(a[l].dis+a[r].dis<=K){
			if(a[l].dis+a[r].dis==K) {
				t++;
				int hh=a[l].dep+a[r].dep;
				cnt[hh]+=flag;
			}
			l++;
		}
		else r--;
	}

后来发现这种情况会漏很多(以前居然用这个A了题,(╥╯^╰╥)感觉被骗)
比如A区间的值都一样,B区间的值也一样
我们在 l l l的时候发现 r r r合法,就累计了一次
而后 l l l就直接加1 ,就漏掉了整个B区间剩下的部分
在这里插入图片描述
正确的打开方式:

	for (int i=1,j=num;i<=j;i++)
	{
		while (j>i && a[i].dis+a[j].dis>K) j--;
		for (int p=j;a[i].dis+a[p].dis==K;p--) cnt[a[i].dep+a[p].dep]+=flag;
	}

3.统计的时候做复杂了,各种T
没有必要每次做完一颗子树就统计
最后统一计算一下即可


就这样………
一个下午没了
o(╥﹏╥)o

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值