【BZOJ1758】重建计划,点分治+单调队列

Time:2016.08.21
Author:xiaoyimi
转载注明出处谢谢


传送门
思路:
UPD 2017.1.18
之前的思路删掉了,因为写的太烂,纯属放屁
刚刚知道新加了一组数据,所以原先的那个T掉了……然后又改了好久,把求重心代码更正+预处理还是跑不动,最后发现Rivendell学长早在一年前预示到了这个问题(orz),接着又去学了下01分数规划的一些知识,才AC的
重新说一下思路好了
我们发现题目所求解的东西符合01分数规划的基本形式,然后就可以二分答案,把边减去答案,然后暴力验证是否存在 u,v ,使得 dis(u,v)0 dep(u)+dep(v)2×dep(lca(u,v))[L,R] ,这样做是 O(n2logn) ;正解考虑点分治,转化成每次重心求解时验证是否存在 u,v ,使得 disu+disv0 depu+depv[L,R] ,这样就可以记录遍历过的子树中深度为 i 的点中dis的最大值 gi ,当前遍历的子树中深度为 j 的点中dis的最大值 fj ,问题就转化成“两个数列 f ,g,求 fj+gi 的最大值,要求 i+j[L,R] ”,这是单调队列的经典问题,一个数列从前向后扫,另一个从后向前加入单调队列就可以了
问题看似完美的解决了,但实际上这是放屁,而且聪哥说网上很多博客都是在放屁,今天我刚知道一些点分治的题目要对子树按大小、深度等信息为关键字来排序,不然没法保证正确的复杂度。
比如这道题我们就要对子树的深度排序(我太懒了所以就写的子树大小= =),不然之前遍历到的子树深度可能会很大,在后面统计信息过程中每次都会扫一遍 g <script type="math/tex" id="MathJax-Element-5598">g</script>数组,导致TLE。
还有一些优化就是把01分数规划的二分改成Dinkelbach算法;把选最优解的过程放到分治过程里面,这样逐层分治子树时答案的下界会越来越大,缩小答案范围,优化时间
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstdlib> 
#include<iostream>
#define M 100004
#define inf 1000000000
#define eps 1e-4
using namespace std;
int root,n,L,R,tot,maxn,cnt;
int first[M],siz[M],mx[M],tmp[M],G[M],S[M];
int Q[M];
struct son{
    int v,w,siz;
    bool operator <(const son other)const
    {
        return siz<other.siz;
    }
}seq[M];
double f[M],g[M],E,ans,inc;
bool vis[M],flag;
char *cp=(char*)malloc(20000000);
struct edge{
    int v,next,w;
}e[M<<1];
struct node{
    int P,dep,fa;
    double dis;
};
queue<node>q;
inline void in(int &x)
{
    for (;*cp<'0'||*cp>'9';++cp);
    for (x=0;*cp>='0'&&*cp<='9';++cp) x=x*10+*cp-48;
}
inline void add(int x,int y,int z)
{
    e[++tot]=(edge){y,first[x],z};first[x]=tot;
    e[++tot]=(edge){x,first[y],z};first[y]=tot;
}
void findroot(int x,int fa)
{
    tmp[++cnt]=x;
    siz[x]=1;
    mx[x]=0;
    for (int i=first[x];i;i=e[i].next)
        if (!vis[e[i].v]&&fa!=e[i].v)
            findroot(e[i].v,x),
            siz[x]+=siz[e[i].v],
            mx[x]=max(mx[x],siz[e[i].v]);
}
inline void getdis(node now,double num)
{   
    q.push(now);
    maxn=1;
    f[1]=max(f[1],now.dis);
    for (;!q.empty();q.pop())
    {
        now=q.front();
        for (int i=first[now.P];i;i=e[i].next)
            if (e[i].v!=now.fa&&!vis[e[i].v])
                q.push((node){e[i].v,now.dep+1,now.P,now.dis+e[i].w-num}),
                f[now.dep+1]=max(f[now.dep+1],now.dis+e[i].w-num),
                maxn=max(maxn,now.dep+1);
    }
}
inline void solve(int x,double num)
{
    int i,j,head,tail,v,w,tmp;
    vis[x]=1;
    for (i=1;i<=siz[x];i++) f[i]=g[i]=-inf;
    g[0]=0;
    for (int i=1;i<=cnt;++i)
    {
        v=seq[i].v;w=seq[i].w;
        getdis((node){v,1,x,w-num},num);
        head=1,tail=0,tmp=0;
        for (j=maxn;j>=1;--j)
            if (f[j]>-inf)
            {
                while (tmp<=siz[x]&&tmp+j<=R)
                {
                    while (head<=tail&&g[Q[tail]]<=g[tmp]) --tail;
                    Q[++tail]=tmp++;
                }                  
                while (head<=tail&&Q[head]+j<L) head++;
                if (head<=tail&&f[j]+g[Q[head]]>=0) inc=max(inc,(f[j]+g[Q[head]])/(j+Q[head]));
            }
        for (j=1;j<=maxn;++j)
            g[j]=max(g[j],f[j]),
            f[j]=-inf;
    }
}
inline void cal(int x)
{
    root=0;
    cnt=0;
    findroot(x,0);
    if (siz[x]<=L) return;
    for (int i=1;i<=cnt;++i)
    {
        x=tmp[i];
        mx[x]=max(mx[x],cnt-siz[x]);
        if (!root||mx[x]<mx[root]) root=x;
    }
    cnt=0;
    findroot(root,0);
    cnt=0;
    for (int j=first[root];j;j=e[j].next)
        if (!vis[e[j].v]) seq[++cnt]=(son){e[j].v,e[j].w,siz[e[j].v]};
    sort(seq+1,seq+cnt+1);
    vis[root]=1;
    double begin,end=E,mid;
    for (;;)
    {
        begin=ans;inc=0;
        solve(root,begin);
        if (inc<=eps) break;
        ans+=inc;
    } 
    for (int i=first[root];i;i=e[i].next)
        if (!vis[e[i].v]) cal(e[i].v);
}
main()
{
    fread(cp,1,20000000,stdin);
    in(n);in(L);in(R); 
    int x,y,z;
    for (int i=1;i<n;++i)
        in(x),in(y),in(z),
        E=max(E,(double)z),
        add(x,y,z);
    cal(1);
    printf("%.3lf",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值