WC 2010 rebuild 重建计划

题目描述就不说了。要题目的请往百度NOI吧。

经典的树的分治,首先,对于答案是分数形式的基本上要使用二分答案,这不是胡扯,详情请见胡伯涛的论文。

那么对于这个题,由于不好估计边分治复杂度(其实边分治是可以过的,好像比点分治要快),对于答案,化成如下形式sigma{e}-ans*s=0,对于一个可行的ans设为val,必然满足sigma{e}-val*s>=0,所以二分。

点分治时,将每个节点到根的路径的权值计算出来,同时记录走过了多少条边。然后按照在每个子树中按照路径走过的边的数目排序,二分答案后验证,合并子树时,第一颗子树在一个数组list中按序保存走过s条边的路径权值最大值,以后的子树中枚举每条路径,利用单调性每次从list中加入一个值用单调队列维护路径的合法性和最优性,在做完一颗子树后这个子树保存的路径与list数组保存的信息合并,作为下一次list使用。

复杂度分析:点分治O(nlogn),每个节点被访问logn+1次,排序O(nlogn)为上限,二分答案验证为O(nlogans),一共做logn次,所以为O(nlognlogans)

Code:(注:不要作为对拍程序,貌似实现有bug)

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
struct element
{
  int key;
  double val;
} que[100005],lis[2][100005];
const double oo=10000000;
const element ori={0,0};
int vis[100005],sta[100005],next[200005],link[200005],head[100005],sum[100005],f[100005],l[100005],r[100005],dis[100005],a[100005],b[100005],count[100005],w[200005],pre[200005],tot[2],hea=0,tai=0;
bool flag[100005];
int time=0,top=0,tail=0,now=0,e=1,up=0,dn=0,n=0;
double ans=0,limitup=0,limitdn=0,mid=0;
inline void add(int u,int v,int tmpw)
{
  next[++e]=head[u];
  head[u]=e;
  link[e]=v;
  w[e]=tmpw;
  pre[next[e]]=e;
  next[++e]=head[v];
  head[v]=e;
  link[e]=u;
  w[e]=tmpw;
  pre[next[e]]=e;
}
inline void update(double &a,double b)
{
  if (a<b) a=b;
}
inline int cal(int s)
{
  ++time;
  f[s]=0;
  top=0;
  vis[s]=time;
  sta[tail=1]=s;
  int ne=0,y=0,x=0,i=0;
  while (top<tail)
    {
      x=sta[++top];
      for (ne=head[x],y=link[ne];ne;ne=next[ne],y=link[ne])
	if (vis[y]!=time)
	  {
	    vis[y]=time;
	    sta[++tail]=y;
	    f[y]=x;
	  }
      sum[x]=0;
      flag[x]=1;
    }
  if (tail==1) return s;
  for (i=tail,x=sta[i];i>=1;i--,x=sta[i])
    {
      sum[x]++;
      sum[f[x]]+=sum[x];
      if (sum[x]<=tail/2) flag[x]=0;
      if (sum[x]>tail/2) flag[f[x]]=0;
      if (flag[x]) return x;
    }
}
void print(int s)
{
  for (;s;s=f[s])
    printf("%d ",s);
  printf("\n");
}
void dfs(int s)
{
  vis[s]=time;
  int ne=0,x=0;
  l[s]=r[s]=++now;
  a[now]=dis[s];
  b[now]=count[s];
  for (ne=head[s],x=link[ne];ne;ne=next[ne],x=link[ne])
    if (vis[x]!=time)
      {
	f[x]=s;
	dis[x]=dis[s]+w[ne];
	count[x]=count[s]+1;
	if (dn<=count[x] && count[x]<=up) update(ans,(double)(dis[x])/count[x]);
	dfs(x);
	r[s]=r[x];
      }
}
inline void swap(int &a,int &b)
{
  int tmp=a;
  a=b;
  b=tmp;
}
inline void qs(int h,int g)
{
  int l=h,k=g,mid=b[(l+k)/2];
  while (l<=k)
    {
      while (b[l]<mid) l++;
      while (b[k]>mid) k--;
      if (l<=k)
	{
	  swap(b[l],b[k]);
	  swap(a[l],a[k]);
	  l++;
	  k--;
	}
    }
  if (l<g) qs(l,g);
  if (h<k) qs(h,k);
}
inline bool com(int st)
{
  return (dn<=st && st<=up);
}
bool check(double mid,int s)
{
  int ne=head[s],x=link[ne],i=0,j=0,now=0,oth=1;
  double tmpw=0;
  tot[0]=tot[1]=hea=tai=0;
  for (i=l[x];i<=r[x];i++)
    {
      if (b[i]==b[i-1]) update(lis[now][tot[now]].val,a[i]-b[i]*mid);
      else
	{
	  lis[now][++tot[now]].val=a[i]-b[i]*mid;
	  lis[now][tot[now]].key=b[i];
	}
    }
  for (ne=next[ne],x=link[ne];ne;ne=next[ne],x=link[ne])
    {
      hea=tai=0;
      j=tot[now];
      for (i=l[x];i<=r[x];i++)
	{
	  tmpw=a[i]-b[i]*mid;
	  for (;!com(que[hea].key+b[i]) && hea<=tai;hea++);
	  for (;lis[now][j].key+b[i]>up && j>0;j--);
	  for (;com(lis[now][j].key+b[i]) && j>0;j--)
	    {
	      for (;lis[now][j].val>que[tai].val && hea<=tai;tai--);
	      que[++tai]=lis[now][j];
	    }
	  if (hea<=tai && que[hea].val+tmpw>-(1e-5) && com(que[hea].key+b[i])) return 1;
	}
      oth=now;
      now=1-now;
      tot[now]=0;
      i=l[x];j=1;
      lis[oth][tot[oth]+1].key=n+1;
      while (i<=r[x] || j<=tot[oth])
	{
	  for (;i<=r[x] && lis[now][tot[now]].key==b[i];i++)
	    update(lis[now][tot[now]].val,a[i]-b[i]*mid);
	  for (;j<=tot[oth] && lis[now][tot[now]].key==lis[oth][j].key;j++)
	    update(lis[now][tot[now]].val,lis[oth][j].val);
	  if (i<=r[x])
	    if (b[i]<=lis[oth][j].key)
	      lis[now][++tot[now]].key=b[i],
		lis[now][tot[now]].val=a[i]-b[i]*mid,i++;
	    else
	      lis[now][++tot[now]]=lis[oth][j],j++;
	  else
	    if (j<=tot[oth])
	      lis[now][++tot[now]]=lis[oth][j],j++;
	}
    }
  return 0;
}
void getans(int s)
{
  if (tail<dn) return;
  ++time;
  now=0;
  dis[s]=0;count[s]=0;
  f[s]=0;
  limitdn=0;
  dfs(s);
  limitup=1e6;
  int ne=0,x=0,me=0;
  for (ne=head[s],x=link[ne];ne;ne=next[ne],x=link[ne])
    qs(l[x],r[x]);
  while (limitdn+1e-5<limitup)
    {
      mid=(limitup+limitdn)/2;
      if (check(mid,s))
	limitdn=mid;
      else
	limitup=mid;
    }
  update(ans,limitdn);
  for (ne=head[s],x=link[ne],me=ne^1;ne;ne=next[ne],x=link[ne],me=ne^1)
    {
      if (head[x]==me)
	{
	  head[x]=next[me];
	  pre[next[me]]=0;
	}
      else
	{
	  next[pre[me]]=next[me];
	  pre[next[me]]=pre[me];
	}
      pre[me]=0;next[me]=0;link[me]=0;w[me]=0;
      getans(cal(x));
    }
}
int main()
{
  freopen("rebuild.in","r",stdin);
  freopen("rebuild.out","w",stdout);
  scanf("%d%d%d",&n,&dn,&up);
  int i=0,u=0,v=0,tmpw=0;
  for (i=1;i<n;i++)
    {
      scanf("%d%d%d",&u,&v,&tmpw);
      add(u,v,tmpw);
    }
  ans=-oo;
  getans(cal(1));
  printf("%.3f",ans);
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值