您的位置:首页 > 其它

#173. 蚯蚓健身操

2016-12-25 21:44 316 查看
题意:给定一颗树(n<=100000),每次询问(q<=200)最大的连通块,满足其中任意点分别到离它最远的叶子结点的距离差的绝对值最大不超过l。

先三次DFS找出离每个点最远的叶子结点的距离。

方法:先DFS出直径,再分别从直径的两端开始DFS,将两次得到的D取max即可。

(证明:假设结论不成立,即存在一个点x,到另一个非直径端点y的距离大于到两个直径端点的距离,画图发现这与直径定义相悖。然后就假装我证好了。)

按D的大小对点进行sort,发现它的逆序就是一个和拓扑序很像的东西。按照这个顺序,不停地计算以该点D为最小值的最大连通块(用并查集维护,不符合要求的点令其sz--,然后放到其父亲所在并查集中)即可。

注意不能直接挂父节点上,因为假设一点x,父亲为fa,par[x]==x,而par[fa]不一定为fa,因为考虑一条链(当然不一定是),D[fa]很可能大于D[x],所以如果直接用fa计算会导致sz出问题,所以要用par[fa]算。讲道理我好像因此调了一个晚上。(其实平安哥已经告诉我了,但我没听出来他说的是这个意思,然后他也以为我处理过了,然后就biubiu……)

不多说了,挂代码。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define ll long long
#define pli pair<ll,int>
#define mkp make_pair
#define X first
#define Y second
#define N 1000005
ll n,m,D
,pos
,Max
,ans;
bool cmp(ll x,ll y){
 return D[x]<D[y];
}
struct POINT{ll K,W,ne;}P
;ll he
,tot;
void add(ll x,ll y,ll z){
 P[++tot]=(POINT){y,z,he[x]};he[x]=tot;
}
ll d[4]
,rt[4],pre
;
void DFS(ll x,ll e,ll k){
 ll p,y;
 for(p=he[x];p;p=P[p].ne)if(p!=e){
  d[k][y=P[p].K]=d[k][x]+P[p].W;
  if(d[k][y]>d[k][rt[k]])rt[k]=y;
  DFS(y,p^1,k);
 }
}
void DFS2(ll x,ll e){
 ll p,y;
 for(p=he[x];p;p=P[p].ne)if(p!=e){
  pre[y=P[p].K]=x;DFS2(y,p^1);
 }
}
ll par
,sz
;
ll getpar(ll x){
 if(par[x]!=x)par[x]=getpar(par[x]);
 return par[x];
}
int main(){
 ll x,y,z,l,i;
 scanf("%lld",&n);tot=1;
 rep(i,2,n){
  scanf("%lld%lld%lld",&x,&y,&z);
  add(x,y,z);add(y,x,z);
 }
 rt[1]=1;DFS(1,0,1);
 rt[2]=rt[1];DFS(rt[1],0,2);
 rt[3]=rt[2];DFS(rt[2],0,3);
 rep(i,1,n)D[pos[i]=i]=max(d[2][i],d[3][i]);
 sort(pos+1,pos+n+1,cmp);
 DFS2(pos[1],0);
 scanf("%lld",&m);
 while(m--){
  scanf("%lld",&l);
  rep(i,1,n)sz[par[i]=i]=1;
  x=n;ans=0;
  per(i,n,1){
   y=pos[i];
   while(D[pos[x]]>l+D[y])--sz[getpar(pos[x--])];
   z=getpar(pre[y]);
   ans=max(sz[y],ans);
   if(y!=z)
   sz[par[y]=z]+=sz[y];
  }
  printf("%lld\n",ans);
 }
 return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: