BZOJ1758 WC2010 重建计划 二分答案、点分治、单调队列

传送门


看到平均数最大,自然地想到二分答案。那么我们的$check$函数就是要求:是否存在一条长度在$[L,U]$的路径,满足其权值和$\geq 0$。

看到长度在$[L,U]$,自然地想到点分治求解。我们考虑如何统计答案,像树的难题那样使用线段树的话,复杂度会变成$nlog^3n$,显然是跑不过这道题的。

我们考虑:将一棵子树内的路径按照长度排序,那么从前往后依次询问子树内每一条路径的贡献的时候,在已经搜过的子树中可以匹配的长度区间是一个单调的区间。那么我们就可以使用单调队列将合并过程优化为$O(n)$,均摊到每一条路径上就是$O(1)$的转移,那么总复杂度就变为了$O(nlogn)$。每一次查询完一棵子树再用这棵子树的路径长度对应的答案更新已经搜过的子树中记录的答案,继续解决之后的问题。

但是先别急着写,考虑这样一种情况:$L=1,U=\frac{N}{2}$,数据先构造了一条点数为$\frac{N}{2}$的链,然后在链的一端构造了一个点数为$\frac{N}{2}$的菊花。你的分治中心显然是会选在菊花的花蕊处,然后你第一棵子树选择访问了那一条链,更新了长度在$1$到$\frac{N}{2}$的最优答案。然后你去访问菊花中的每一个点的时候,每一次都需要跑$\frac{N}{2}$次对单调队列进行初始化,然后你这一层分治的复杂度就变成了$O(N^2)$。

那么这个问题的根源是什么呢?在于每一次单调队列初始化的复杂度实际上是$O(min(U - 1 , maxLen))$,其中$maxLen$为之前访问的路径的最长长度。如果我们在本身$U$就比较大的情况下,先选择将$maxLen$变到很大,然后再不断地进行单调队列的操作,那复杂度就自然上天了。

那么解决方法是什么呢?一个有效的方法是按照子树的$maxLen$从小到大访问子树,这样每一次分治中单调队列初始化的复杂度总和就能控制在$O(\text{分治区域大小})$级别,就有正确的$O(nlog^2n)$的复杂度了。

因为这道题卡常较为严重,推荐能够初始化的内容尽量初始化,如果每一次分治的过程中重新求很多东西你就会获得$20pts$的好成绩qwq

 

#include<bits/stdc++.h>
#define ld long double
#define eps 1e-8
#define int long long
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    bool f = 0;
    char c = getchar();
    while(c != EOF && !isdigit(c)){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    while(c != EOF && isdigit(c)){
        a = (a << 3) + (a << 1) + (c ^ '0');
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 100007;
struct Edge{
    int end , upEd , w;
}Ed[MAXN << 1];
int head[MAXN] , size[MAXN];
int N , L , U , cntEd , nowSize , minSize , minInd;
vector < vector < int > > be[MAXN];
vector < int > temp , pot;
deque < int > q;
bool vis[MAXN];

bool cmp(vector < int > a , vector < int > b){
    return a.size() < b.size();
}

inline void addEd(int a , int b , int c){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    Ed[cntEd].w = c;
    head[a] = cntEd;
}

void getSize(int x){
    vis[x] = 1;
    ++nowSize;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(!vis[Ed[i].end])
            getSize(Ed[i].end);
    vis[x] = 0;
}

void getRoot(int x){
    vis[x] = size[x] = 1;
    int maxN = 0;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(!vis[Ed[i].end]){
            getRoot(Ed[i].end);
            size[x] += size[Ed[i].end];
            maxN = max(maxN , size[Ed[i].end]);
        }
    maxN = max(maxN , nowSize - size[x]);
    if(maxN < minSize){
        minSize = maxN;
        minInd = x;
    }
    vis[x] = 0;
}

void dfs(int x , int dep , int w){
    if(dep > U)
        return;
    if(temp.size() == dep)
        temp.push_back(w);
    else
        temp[dep] = max(temp[dep] , w);
    vis[x] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(!vis[Ed[i].end])
            dfs(Ed[i].end , dep + 1 , w + Ed[i].w);
    vis[x] = 0;
}

void init(int x){
    nowSize = 0;
    minSize = 0x7fffffff;
    getSize(x);
    getRoot(x);
    int root = minInd;
    vis[root] = 1;
    for(int i = head[root] ; i ; i = Ed[i].upEd)
        if(!vis[Ed[i].end]){
            temp.push_back(0);
            dfs(Ed[i].end , 1 , Ed[i].w);
            be[root].push_back(temp);
            temp.clear();
        }
    sort(be[root].begin() , be[root].end() , cmp);
    for(int i = head[root] ; i ; i = Ed[i].upEd)
        if(!vis[Ed[i].end])
            init(Ed[i].end);
}

inline vector < int > merge(vector < int > a , vector < int > b){
    if(a.size() < b.size())
        swap(a , b);
    for(int i = 0 ; i < b.size() ; ++i)
        a[i] = max(a[i] , b[i]);
    return a;
}

inline bool judge(vector < int > a , vector < int > b , ld mid){
    if(!b.size())
        return 0;
    q.clear();
    int p = 0;
    for(int i = b.size() - 1 ; i >= 0 ; --i){
        while(p < a.size() && p + i <= U){
            while(!q.empty() && a[q.back()] - mid * q.back() < a[p] - mid * p)
                q.pop_back();
            q.push_back(p++);
        }
        while(!q.empty() && q.front() + i < L)
            q.pop_front();
        if(!q.empty() && a[q.front()] - mid * q.front() + b[i] - mid * i > -eps)
            return 1;
    }
    return 0;
}

bool check(ld mid){
    for(int i = 1 ; i <= N ; ++i){
        if(!be[i].size())
            continue;
        pot.clear();
        for(int j = 0 ; j < be[i].size() ; ++j){
            if(judge(pot , be[i][j] , mid))
                return 1;
            pot = merge(pot , be[i][j]);
        }
    }
    return 0;
}

signed main(){
#ifndef ONLINE_JUDGE
    freopen("4292.in" , "r" , stdin);
    //freopen("4292.out" , "w" , stdout);
#endif
    N = read();
    L = read();
    U = read();
    ld L = 1e6 , R = 0;
    for(int i = 1 ; i < N ; ++i){
        int a = read() , b = read() , c = read();
        addEd(a , b , c);
        addEd(b , a , c);
        L = min(L , (ld)c);
        R = max(R , (ld)c);
    }
    init(1);
    while(R - L > eps){
        ld mid = (L + R) / 2;
        check(mid) ? L = mid : R = mid;
    }
    printf("%.3Lf" , L);
    return 0;
}

 

posted @ 2018-12-06 09:37  cjoier_Itst  阅读(314)  评论(0编辑  收藏  举报