Codeforces 804D

首先对于每棵树求出从每个点出发最长路径的长度(他们中最大值为直径)
从小到大排序,并记录前缀和。一开始我以为处理询问时有厉害的做法,但事实上就是利用启发式的思想,挑选点较小的那一棵树,然后枚举所有的点,由于答案是max(f1+f2+1,max(d1,d2)),所以我们要在另一颗树中二分答案,找到大于max(d1,d2)的位置,再根据前缀和计算答案。每次处理询问时用map保存下结果,这样复杂度就得到了保证。

计算最长路径那个部分需要用到两次dfs,第一次直接记录它往下走的最长长度,第二次dfs相当于换根,把它调整为根计算答案,dfs完后还要调整回来,这也是换根的树形dp中常见的做法

include<bits/stdc++.h>
#define ll long long
#define ld double
using namespace std;
const int maxn=100000+10;
int rt[maxn],n,m,q,down[maxn],sz[maxn],d[maxn],zj[maxn],cnt;
vector<int> g[maxn];
vector<int> h[maxn];
vector<ll> sum[maxn];
map<int,ld> mp[maxn];
void dfs1(int p,int fa)
{
  sz[rt[p]]++;
  for(int i=0;i<g[p].size();i++)
  {
    int v=g[p][i];if(v==fa) continue;
    rt[v]=rt[p];dfs1(v,p);
    down[p]=max(down[p],down[v]+1);
  }
}
void dfs2(int p,int fa)
{
  int mx1=-1,mx2=-1;d[p]=down[p];
  for(int i=0;i<g[p].size();i++)
  {
    int v=g[p][i];
    if(down[v]>mx1) 
    {
      mx2=mx1;
      mx1=down[v];
    }
    else if(down[v]>mx2) mx2=down[v];
  }

  for(int i=0;i<g[p].size();i++)
  {
    int v=g[p][i],val=down[p],v2=down[v];if(v==fa) continue;
    if(down[v]==mx1) down[p]=mx2+1;
    else down[p]=mx1+1;
    down[v]=max(down[v],down[p]+1);
    dfs2(v,p);
    down[p]=val;
    down[v]=v2;
  }
}
int main()
{
  //freopen("test.in","r",stdin);
  scanf("%d%d%d",&n,&m,&q);
  for(int i=1;i<=m;i++)
  {
    int x,y;scanf("%d%d",&x,&y);
    g[x].push_back(y);
    g[y].push_back(x);
  }
  for(int i=1;i<=n;i++)
    if(!rt[i])
    {
      rt[i]=++cnt;
      dfs1(i,0);
      dfs2(i,0);
    }
  for(int i=1;i<=n;i++)
  {
    zj[rt[i]]=max(zj[rt[i]],d[i]);
    h[rt[i]].push_back(d[i]);
  }
  for(int i=1;i<=cnt;i++)
  {
    sort(h[i].begin(),h[i].end());
    sum[i].push_back(h[i][0]);
    for(int j=1;j<h[i].size();j++)
      sum[i].push_back(sum[i][j-1]+h[i][j]);
  }
  for(int i=1;i<=q;i++)
  {
    int x,y,u,v;scanf("%d%d",&x,&y);
    u=rt[x];v=rt[y];
    if(u==v) puts("-1");
    else
    {
      if(sz[u]>sz[v]||(sz[u]==sz[v]&&u>v)) swap(u,v);
      if(mp[u].count(v)) printf("%.9lf\n",mp[u][v]);
      else
      {
        int maxx=max(zj[u],zj[v]);ll res=0;
        for(int j=0;j<h[u].size();j++)
        {
          int p=h[u][j];
          if(h[v][sz[v]-1]+p+1<=maxx) res+=(ll)sz[v]*maxx;
          else
          {
            int nx=upper_bound(h[v].begin(),h[v].end(),maxx-1-p)-h[v].begin();
            res+=(ll)nx*maxx+(ll)(sz[v]-nx)*(p+1);
            res+=sum[v][sz[v]-1];
            if(nx) res-=sum[v][nx-1];
          }
        }
        ld out=(ld)res/((ld)sz[u]*sz[v]);
        printf("%.9lf\n",out);
        mp[u][v]=out;
      }
    }
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值