[BZOJ1758][WC2010]重建计划(01分数规划+点分治+单调队列)

本文介绍了一道涉及分数规划和点分治算法的题目,通过二分查找和单调队列优化,解决寻找最优平均值的问题。算法最终复杂度为O(nlog²n)。

题目:

我是超链接

题解:

首先平均数最大,平均数其实是 a1 ∑ a ∑ 1 ,求最值是01分数规划的问题
二分mid,如果有更优的值 (amid)>0 ∑ ( a − m i d ) > 0 ,那么我们的check途径就是通过点分治判断长度在[l,r]区间内有没有一条链的和>0,链的长度为a[i]-mid

而check的过程中对于每个当前子树,维护每个深度的最优值,我们只需要取之前记录的最优的一项更新就好了。我们可以把当前子树中的值按深度从大到小来更新答案,这个过程中使用单调队列维护一个深度下降权值序列,然后每次取队首更新答案即可,每次查看队首的时候把已经相加超过R的踢出去,然后查看队尾,如果比这次要添加的L-j(即下限)更小的话就T出去

这个过程是线性的,而对于全部子树 i 深度最长距离的数组的更新也只是取个max,它也是线性的。 O(nlog2n) O ( n l o g 2 n )

这题已经调的我不想写题解了,只能说ljBZOJ

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=150005;
#define INF 1e9
int tot,nxt[N*2],L,R,point[N],v[N*2],f[N],size[N],root,sum,deep[N],DEP,dep,q[N];
double c[N*2],a[N],b[N],mid,ans;bool vis[N];
int read()
{
    char ch=getchar();int x=0;
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x;
}
void addline(int x,int y,double z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z; 
}
void getroot(int x,int fa)
{
    f[x]=0; size[x]=1;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && !vis[v[i]])
      {
        getroot(v[i],x);
        size[x]+=size[v[i]];
        f[x]=max(f[x],size[v[i]]);
      }
    f[x]=max(f[x],sum-size[x]);
    if (f[x]<f[root]) root=x;
}
void getdeep(int x,int fa)
{
    deep[x]=deep[fa]+1;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa && !vis[v[i]]) getdeep(v[i],x);
    DEP=max(DEP,deep[x]);
}
void dfs(int x,int fa,double sum)
{
    if (deep[x]>R) return;
    a[deep[x]]=max(a[deep[x]],sum);
    for (int i=point[x];i;i=nxt[i])
      if (!vis[v[i]] && v[i]!=fa) dfs(v[i],x,sum+c[i]-mid);
    dep=max(dep,deep[x]);
}
bool check()
{
    double maxx=-INF;int now=0;
    b[0]=0;
    for (int i=1;i<=DEP;i++) b[i]=-INF;
    for (int i=point[root];i;i=nxt[i])
      if (!vis[v[i]])
      {
        int l=1,r=0;dep=0;a[0]=0;
        for (int j=1;j<=DEP;j++) a[j]=-INF;
        dfs(v[i],root,c[i]-mid);

        if (now) q[++r]=now;
        for (int j=1;j<=dep;j++)
        {
            if (j>R) break;
            while (l<=r && q[l]+j>R) l++;
            if (j<=L && L-j<=DEP)
            {
                while (l<=r && b[q[r]]<b[L-j]) r--;
                q[++r]=L-j;
                maxx=max(maxx,b[q[l]]+a[j]);
            }else if (j>L) maxx=max(maxx,b[q[l]]+a[j]); 
        }
        for (int j=1;j<=dep;j++)
        {
            b[j]=max(b[j],a[j]);
            if (j>=L && j<=R && b[now]<b[j]) now=j;
        }
      }
    return maxx>0;
}
void work()
{
    DEP=0;////
    getdeep(root,0);
    double l=ans,r=1e6;
    while (r-l>1e-4)
    {
        mid=(l+r)/2;
        if (check()) l=mid;else r=mid;
    }
    vis[root]=1;ans=l;
    for (int i=point[root];i;i=nxt[i])
      if (!vis[v[i]])
      {
        sum=size[v[i]]; if (sum<L) continue;
        root=0; f[0]=INF; getroot(v[i],0);
        work();
      }
}
int main()
{
    int n;n=read();L=read();R=read(); 
    for (int i=1;i<n;i++) 
    {
        int x,y,z;x=read();y=read();z=read();
        addline(x,y,z);
    }
    deep[0]=-1;
    sum=n; root=0; f[0]=INF; getroot(1,0);
    work();
    printf("%.3lf",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值