【WC2010】【BZOJ1758】重建计划

【题目链接】

【前置技能】

  • 二分答案
  • 单调队列
  • 点分治

【题解】

  • 先二分答案 m i d ,将每条边的权值减去 m i d ,问题转化为判断在树上是否存在一条长度在 L R 之间的路径,其权值和非负。
  • 路径问题,用点分治处理。记录下每个分治中心出发的各个长度的路径的最大权值。在处理分治中心的某个子树的时候,在已经处理出来的数组中用单调队列查询,然后将这棵子树的信息合并进数组。
  • 分析一下复杂度,发现在一些情况下可能会退化。举个极端的例子,分治中心处第一棵被访问到的子树的深度为 R 1 ,以后的每一棵子树都只有一个点,那么每一次的单调队列都会把R个元素都访问一遍,点分治的时间复杂度退化为 O ( N 2 ) 。将子树按深度从小到大排序,就避免了这个问题。
  • 可以将点分树存起来,预先把子树排个序,卡一卡常数。注意排序要 O ( N ) 基数排序,不然复杂度多一个 l o g N ,而且还不如直接线段树维护一发写得方便。
  • 时间复杂度 O ( N l o g 2 N )
  • 或者如果觉得这样处理来处理去太麻烦的话,也可以选择长链剖分。

【代码】

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LL  long long
#define MAXN    100010
#define EPS 1e-4
using namespace std;
int n, L, R, fla, ROOT, arrsize, cursize;
int size[MAXN], wei[MAXN], root, vis[MAXN], dep[MAXN];
double maxw, f[MAXN], cur[MAXN];
struct info{int v; double w;};
vector <info> a[MAXN], c[MAXN];
vector <int> b[MAXN];

template <typename T> void chkmin(T &x, T y){x = min(x, y);}
template <typename T> void chkmax(T &x, T y){x = max(x, y);}
template <typename T> void read(T &x){
    x = 0; int f = 1; char ch = getchar();
    while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
    while (isdigit(ch)) {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}

void getroot(int pos, int dad, int tot){
    wei[pos] = 0, size[pos] = 1, dep[pos] = 0;
    for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
        int son = a[pos][i].v;
        if (son != dad && !vis[son]){
            getroot(son, pos, tot);
            size[pos] += size[son];
            chkmax(wei[pos], size[son]); 
            chkmax(dep[pos], dep[son]);
        }
    }
    ++dep[pos]; 
    chkmax(wei[pos], tot - size[pos]);
    if (wei[pos] < wei[root]) root = pos;
}

void init(int pos, int tot){
    vis[pos] = 1;
    root = 0; getroot(pos, 0, tot);
    static int cnt[MAXN], rk[MAXN], tmpv[MAXN], maxn, sizeson;
    static double tmpw[MAXN];
    maxn = sizeson = 0;
    for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
        int son = a[pos][i].v;
        if (!vis[son]){
            ++sizeson;
            chkmax(maxn, dep[son]);
            ++cnt[dep[son]];
        }
    }
    for (int i = 1; i <= maxn; ++i)
        cnt[i] += cnt[i - 1];
    for (int i = 0, si = a[pos].size(); i < si; ++i){
        int son = a[pos][i].v;
        if (!vis[son]) rk[cnt[dep[son]]--] = i;
    }
    for (int i = 1; i <= sizeson; ++i)
        tmpv[i] = a[pos][rk[i]].v, tmpw[i] = a[pos][rk[i]].w;
    for (int i = 1; i <= sizeson; ++i)
        c[pos].push_back((info){tmpv[i], (double)tmpw[i]});
    for (int i = 1; i <= maxn; ++i)
        cnt[i] = 0;
    for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
        int son = a[pos][i].v;
        if (!vis[son]){
            root = 0; getroot(son, 0, size[son]);
            b[pos].push_back(root);
            init(root, size[son]); 
        }
    }
}

void getans(int pos, int dad, double val, int dep){
    chkmax(cur[dep], val);
    chkmax(cursize, dep);
    for (unsigned i = 0, si = a[pos].size(); i < si; ++i){
        int son = a[pos][i].v; double wth = a[pos][i].w;
        if (!vis[son] && son != dad){
            getans(son, pos, val + wth, dep + 1);
        }
    }
}

void work(int pos){
    if (fla) return;
    vis[pos] = 1;
    arrsize = 0;
    for (unsigned i = 0, si = c[pos].size(); i < si; ++i){
        int son = c[pos][i].v; double wth = c[pos][i].w;
        if (!vis[son]){
            cursize = 0;
            getans(son, 0, wth, 1);
            static int q[MAXN], l, r;
            l = 0, r = -1;
            for (int j = min(arrsize, R - 1); j >= max(0, L - 1); --j){
                while (l <= r && f[q[r]] < f[j]) --r;
                q[++r] = j;
            }
            for (int j = 1; j <= cursize; ++j){
                while (l <= r && q[l] > R - j) ++l;
                while (L - j >= 0 && L - j <= arrsize && l <= r && f[q[r]] < f[L - j]) --r;
                q[++r] = L - j;
                if (l <= r && cur[j] + f[q[l]] >= 0) fla = 1;
            }
            chkmax(arrsize, cursize);
            for (int j = 1; j <= arrsize; ++j)
                chkmax(f[j], cur[j]), cur[j] = -INF;
        }
    }
    for (int i = 1; i <= arrsize; ++i)
        f[i] = -INF;
    for (unsigned i = 0, si = b[pos].size(); i < si; ++i)
        work(b[pos][i]);
}

bool ok(double x){
    fla = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 0, si = a[i].size(); j < si; ++j)
            a[i][j].w -= x;
    for (int i = 1; i <= n; ++i)
        for (int j = 0, si = c[i].size(); j < si; ++j)
            c[i][j].w -= x;
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; ++i)
        f[i] = cur[i] = -INF;
    work(ROOT);
    for (int i = 1; i <= n; ++i)
        for (int j = 0, si = a[i].size(); j < si; ++j)
            a[i][j].w += x;
    for (int i = 1; i <= n; ++i)
        for (int j = 0, si = c[i].size(); j < si; ++j)
            c[i][j].w += x;
    return fla;
}

int main(){
    read(n), read(L), read(R);
    for (int i = 1; i < n; ++i){
        int u, v, w; read(u), read(v), read(w);
        chkmax(maxw, (double)w);
        a[u].push_back((info){v, (double)w});
        a[v].push_back((info){u, (double)w});
    }
    memset(vis, 0, sizeof(vis));
    size[0] = wei[0] = n;
    root = 0; getroot(1, 0, n); ROOT = root;
    init(root, n);
    double l = 0, r = maxw;
    while (l + EPS < r){
        double mid = (l + r) / 2;
        if (ok(mid)) l = mid;
        else r = mid;
    }
    printf("%.3lf\n", (l + r) / 2);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/six_solitude/article/details/80601450