【BZOJ1758】【Wc2010】重建计划 分数规划+树分治单调队列check

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Vmurder/article/details/44418207

广告:

#include <stdio.h>
int main()
{
    puts("转载请注明出处[vmurder]谢谢");
    puts("网址:blog.csdn.net/vmurder/article/details/44418207");
}

题解:

首先比较显然会想到分数规划模型。
不太好想,先放过。

我们树分治处理经过每个点的路径。
然后对于在[L,R]区间内的每个长度记录最长距离。
然后每棵子树跟之前记录的数组处理一下,算出当前这棵子树中点为一端点,经过根节点(重心)的最优答案,然后再更新记录最长距离的数组。

貌似很科学,但是这个更新答案的过程,实际上是O(n2)的,并不能接受。

所以就有了下述神奇的处理方法:
我们进行分数规划,二分答案,这样新的边权赋值为len-ans(mid),然后check答案是否>0。。
而check的过程中对于每个当前子树中的最优深度函数值,我们只需要取之前记录的最优的一项更新就好了。我们可以把当前子树中的函数值按深度从小到大来更新答案,这个过程中使用单调队列维护一个深度上升权值(新赋值的权值)上升的序列,然后每次取队尾更新答案即可。
这个过程是线性的,而对于全部子树 i 深度最长距离的数组的更新也只是取个max,它也是线性的。

这样就可以在O(nlog2n)的时间内出解了。

二分写在里面还是写在外面?

然后有一个细节问题,就是二分写在在对每个重心处理时还是写在树分治的外面的问题。

网上很多都说写在里面快,而我的同学 PoPoQQQ 实测是写在外面快。
所以当然是写在外面快!!!
(并不是因为“实测”,而是因为这是大爷说的!!! 2333)

下面给出一定的分析。
首先写在里面的话是对于每个重心更新答案之后,之后的重心在处理时下边界会增大,这样的话实际上时间受影响于每次在当前重心更新的幅度。
而写在外面的话,则是严格完整时间复杂度?并不是的。我们在某个重心进行处理时发现符合要求的答案时,可以直接跳出。

写在里面是一点一点调整答案,但是调整的幅度欠佳,因为还有一个上界未被改变(这个我不确定是否有下调上界的剪枝,没有细想,不要D我)。
而写在外面则是大步跳,每次得到mid以后期望很快得到一个重心可以求出符合答案的解,也就是很多重心不需要被处理,剪枝的幅度比较大。

代码:

这个代码是刚开始不知道单调队列分数规划shenmegui,然后一点点扒的别人代码,发现看不懂都是因为人家代码太渣太丑了,其实思想水得要命,而且实现上也非常简单。。

总之这个代码也是一款又渣又丑的产品,如果要扒代码请一定要跳过这个代码。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 101000
#define INF 1e13
#define eps 1e-4
#define V e[i].v
using namespace std;
struct Eli
{
    int v,len,next;
}e[N<<1];
int head[N],cnt;
inline void add(int u,int v,int len)
{
    e[++cnt].v=v;
    e[cnt].len=len;
    e[cnt].next=head[u];
    head[u]=cnt;
}

int can[N],size[N],deep[N],q[N];
int L,R,tl,tr;
int n,length,dep;
int root,DEP;
double a[N],b[N],sum[N];
double ans,mid;
inline void getroot(int x,int p)
{
    int d=0;
    size[x]=1;
    for(int i=head[x];i;i=e[i].next)
        if(can[V]&&V!=p)
        {
            getroot(V,x);
            size[x]+=size[V];
            d=max(d,size[V]);
        }
    d=max(n-size[x],d);
    if(d<length)length=d,root=x;
}
inline void dfs(int x,int p)
{
    size[x]=1;
    deep[x]=deep[p]+1;
    for(int i=head[x];i;i=e[i].next){
        if(can[V]&&V!=p){
            dfs(V,x);
            size[x]+=size[V];
        }
    }
    if(deep[x]>DEP)DEP=deep[x];
}
inline void dfs1(int x,int p)
{
    if(deep[x]>R)return; // 此深度不能经过重心
    a[deep[x]]=max(a[deep[x]],sum[x]);
    for(int i=head[x];i;i=e[i].next)
    {
        if(can[V]&&V!=p)
        {
            sum[V]=sum[x]+e[i].len-mid;
            dfs1(V,x);
        }
    }
    if(deep[x]>dep)dep=deep[x];
}
bool check()
{
    double mx,tmp,maxx;
    int pos,i,j;

    b[0]=0;
    for(i=1;i<=DEP;i++)b[i]=-INF;
    tmp=-INF;maxx=-INF;
    int maxp=0;
    for(i=head[root];i;i=e[i].next)if(can[V])
    {
        tl=tr=0;
        a[0]=0;
        for(j=1;j<=DEP;j++)a[j]=-INF;
        // 枚举深度 a[i]记录此子树深度为i的最大权
        dep=0;
        sum[V]=e[i].len-mid;
        // 目前只要处理出深度i时的最长,所以mid不用*路径长度
        dfs1(V,root);
        // 处理a数组,同时记录当前子树中最深深度。
        if(maxp)q[++tr]=maxp;
        for(j=1;j<=dep;j++)
        {
            if(j>R)break;
            if(tl<tr&&q[tl+1]>R-j)tl++;
            if(L>=j&&L-j<=DEP)
            {
                while(tl<tr&&b[q[tr]]<b[L-j])tr--;
                q[++tr]=L-j;
                tmp=max(tmp,b[q[tl+1]]+a[j]);
            }
            else if(j>L)tmp=max(tmp,b[q[tl+1]]+a[j]);
        }
        for(j=1;j<=dep;j++) // b_i是当前整棵树的深度i最大权
        {
            b[j]=max(b[j],a[j]);
            if(j>=L&&j<=R&&maxx<b[j])
                maxx=b[j],maxp=j;
        }
    }
    return tmp>0;
}
void work(int x)
{
    length=n,getroot(x,0); // 找树的重心
    if(n<=L)return;
    DEP=0;
    dfs(root,0); // 处理每个点以重心为根的deep和深度,同时记录最大深度
    double l=ans,r=1e10;
    while(l<r-eps) // 分数规划
    {
        mid=(l+r)/2;
        if(check())l=mid;
        else r=mid;
    }
    ans=l;
    can[root]=0; // 递归分治
    for(int i=head[root];i;i=e[i].next)
        if(can[V])n=size[V],work(V);
}
int main()
{
    scanf("%d%d%d",&n,&L,&R);
    int x,y,z;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z),add(y,x,z);
    }
    memset(can,-1,sizeof can);
    deep[0]=-1;
    work(1);
    printf("%.3lf\n",ans);
    return 0;
}
展开阅读全文

没有更多推荐了,返回首页