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

本文介绍了解决特定问题的一种高效算法思路——利用01分数规划结合点分治技术。通过二分查找和图论中的点分治策略,文章详细解释了如何将原始问题转化为更易于解决的形式,并提出了多项优化措施。

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 数组,导致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);
}
### 回答1: bzoj作为一个计算机竞赛的在线评测系统,不仅可以提供大量的题目供程序员练习和学习,还可以帮助程序员提升算法和编程能力。为了更好地利用bzoj进行题目的学习和刷题,制定一个bzoj做题计划是非常有必要的。 首先,我们需要合理安排时间,每天留出一定的时间来做bzoj的题目。可以根据自己的时间安排,每天挑选适量的题目进行解答。可以先从难度较低的题目开始,逐渐提高难度,这样既能巩固基础知识,又能挑战自己的思维能力。 其次,要有一个计划和目标。可以规划一个每周或每月的题目数量目标,以及每个阶段要学习和掌握的算法知识点。可以根据bzoj的题目分类,如动态规划、图论、贪心算法等,结合自己的实际情况,有针对性地选择题目进行学习。 此外,要充分利用bzoj提供的资源。bzoj网站上有很多高质量的题解和优秀的解题代码,可以参考和学习。还有相关的讨论区,可以与其他程序员交流和讨论,共同进步。 最后,要坚持并保持思考。做题不是单纯为了刷数量,更重要的是学会思考和总结。遇到难题时,要有耐心,多思考,多尝试不同的解法。即使不能一次性解出来,也要学会思考和分析解题过程,以及可能出现的错误和优化。 总之,bzoj做题计划的关键在于合理安排时间、制定目标、利用资源、坚持思考。通过有计划的刷题,可以提高算法和编程能力,并培养解决问题的思维习惯,在计算机竞赛中取得更好的成绩。 ### 回答2: bzoj做题计划是指在bzoj这个在线测评系统上制定一套学习和刷题的计划,并且将计划记录在excel表格中。该计划主要包括以下几个方面的内容。 首先是学习目标的设定。通过分析自己的水平和知识缺口,可以设定一个合理的目标,比如每天解决一定数量的题目或者提高特定的算法掌握程度。 其次是题目选择的策略。在excel表格中可以记录下自己选择的题目编号、题目类型和难度等信息。可以根据题目的类型和难度来安排每天的刷题计划,确保自己可以逐步提高技巧和解题能力。 然后是学习进度的记录和管理。将每天的完成情况记录在excel表格中,可以清晰地看到自己的学习进度和任务完成情况。可以使用图表等功能来对学习进度进行可视化展示,更好地管理自己的学习计划。 同时,可以在excel表格的备注栏中记录下每道题目的解题思路、关键点和需要复习的知识点等信息。这样可以方便自己回顾和总结,巩固所学的知识。 最后,可以将excel表格与其他相关资料进行整合,比如算法教材、题目解析和学习笔记等。这样可以形成一个完整的学习档案,方便自己进行系统的学习和复习。 总之,bzoj做题计划excel的制定和记录可以帮助我们更加有条理和高效地进行学习和刷题。通过合理安排学习目标和题目选择策略,记录学习进度和思路,并整合其他学习资料,我们可以提高自己的解题能力,并在bzoj上取得更好的成绩。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值