BZOJ 3612 HEOI2014 大工程 树链剖分求LCA的优越

虚树神马的网上已经讲了不少了 , 这里就多提啦 , 推荐看这个小伙伴的博客

本题是一个虚树的裸题 , 就不提示了。一般来说我不写这种题解烂大街的题目 , 但这次尝试打破了我以前的一个思维惯性 , 关于时间复杂度的误区。

为什么这题我写树链剖分求LCA呢? 因为我算错空间辣-_-#

当时我正在算倍增算法的空间 , 1000000202108 然后我就不敢写 nlog(n) 的倍增啦QAQ

不要紧 , 反正链剖空间低 , 我只有委曲求全的写了 nlog(n)2 链剖 , 发现时间还挺快的 , 比hzwer的快2s. 当时就想 ,不会吧 , 我写的可是 nlog(n)2 的方法啊……

此时我想起某次写倍增被卡时(最后被逼把倍增改成3进制才过)的经历 , 这次我决定试一试这两者的时间差别。 当我把 lca 改成倍增写法后 , 时间狂翻3倍QAQ。 我尝试了各种优化措施 , 但效果均不明显 , 难道 nlog(n) 的倍增算法常数真的这么大吗? 其实仔细看看倍增的结构 , 你会发现 , 常数很小的样子 , 就是几个循环了事啊……

所以 , 这只能说明树剖的常数小 , 至于有多小 , 大家都可以脑补(某集训队论文分析 , 常数最大 12 ) , 但如果查询的两个点很接近 , 那么可以快速出解。 但是倍增的时间复杂度的自由空间很小 , 所以在此题求 lca 一个理论上 nlog(n)2 的算法战胜了一个理论上 nlog(n) 的算法。

我的代码里有两种写法 , 大家可以随意调试:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <cassert>

using namespace std;
const int maxn = 1e6+1e2;
const int INF = 0x3f3f3f3f;
int n , m , dfsCnt , fa[maxn] , id[maxn] , bl[maxn] , dep[maxn] , Size[maxn] , book[maxn] , w[maxn];
vector<int> g[maxn] , f[maxn];

//int ace[maxn][20];

//void pre()
//{
//  for(int i=1;i<=n;i++) ace[i][0] = fa[i];
//  for(int i=1,j;i<20;i++) for(int k=1;k<=n;k++)
//  {
//      j = ace[k][i-1];
//      if(!j) continue;
//      ace[k][i] = ace[j][i-1];
//  }
//}

__inline int re() {
    int n = 0, ch = getchar(); bool flag = false;
    while(!isdigit(ch)) flag |= ch == '-', ch = getchar();
    while(isdigit(ch)) n = n * 10 + ch - '0', ch = getchar();
    return flag ? -n : n;
}

void dfs(int x)
{
    id[x] = ++dfsCnt;
    Size[x] = 1;
    for(int i=0,t;i<g[x].size();i++)
    {
        t = g[x][i];
        if(t == fa[x]) continue;
        fa[t] = x;
        dep[t] = dep[x] + 1;
        dfs(t);
        Size[x] += Size[t];
    }
}

void dfs(int x , int num)
{
    bl[x] = num;

    int mx=0 , w;
    for(int i=0,t;i<g[x].size();i++)
    {
        t = g[x][i];
        if(t == fa[x]) continue;
        if(mx < Size[t]) w = t , mx = Size[t];
    }

    if(mx) dfs(w, num);

    for(int i=0,t;i<g[x].size();i++)
    {
        t = g[x][i];
        if(t == fa[x] || t == w) continue;
        dfs(t, t);
    }
}

//int lca(int x , int y)
//{
//  if(dep[x] > dep[y]) swap(x, y);
//  for(int i=log2(dep[y]+1);i>=0;i--) if(ace[y][i] && dep[ace[y][i]] >= dep[x]) y = ace[y][i];
//  
//  if(x == y) return x;
//  
//  for(int i=log2(dep[x]+1),j,k;i>=0;i--)
//  {
//      j = ace[x][i];
//      k = ace[y][i];
//      if(j == k) continue;
//      x = j; y = k;
//  }
//  
//  return fa[x];
//}

int lca(int x , int y)
{
    int f1 , f2;
    while(true)
    {
        f1 = bl[x];
        f2 = bl[y];
        if(f1 == f2) break;

        if(dep[f1] < dep[f2]) y = fa[f2];
        else x = fa[f1];
    }

    return dep[x] < dep[y] ? x : y;
}

long long r1;
int r2 , r3 , mn[maxn] , mx[maxn] , s[maxn];

void getRes(int x)
{
    if(book[x])
    {
        s[x] = 1;
        r1 += 1LL*dep[x]*m;
        mx[x] = mn[x] = dep[x];
    }

    for(int i=0,j;i<f[x].size();i++)
    {
        j = f[x][i];
        getRes(j);

        s[x] += s[j];
        r1 -= 1LL*s[j]*s[j]*(dep[j] - dep[x]);
        r2 = min(r2 , mn[x] - dep[x] + mn[j] - dep[x]);
        r3 = max(r3 , mx[x] - dep[x] + mx[j] - dep[x]);
        mn[x] = min(mn[x] , mn[j]);
        mx[x] = max(mx[x] , mx[j]);
    }
    f[x].clear();
}

void clear(int x)
{
    for(int i=0,j;i<f[x].size();i++)
    {
        j = f[x][i];
        clear(j);
    }

    mn[x] = INF; mx[x] = -INF; s[x] = 0;
}

int q[maxn] , t;
bool cmp(int x , int y) { return id[x] < id[y]; }
void solve()
{
    sort(w+1, w+1+m, cmp);

    t = 0;
    for(int i=1,j;i<=m;i++)
    {
        if(!t) q[t++] = w[i];
        else 
        {
            if(lca(q[t-1], w[i]) == q[t-1]) q[t++] = w[i];
            else 
            {
                while(t>1 && dep[lca(q[t-2], w[i])] < dep[q[t-2]])
                {
                    f[q[t-2]].push_back(q[t-1]);
                    t--;
                }

                f[j = lca(q[t-1], w[i])].push_back(q[t-1]) , t--;
                if(!t || q[t-1]!=j) q[t++] = j;
                if(!t || q[t-1]!=w[i]) q[t++] = w[i];
            }
        }
    }
    while(t>1) f[q[t-2]].push_back(q[t-1]) , t--;

    r1 = 0; r2 = INF; r3 = -INF;
    clear(q[0]);
    getRes(q[0]);
    printf("%lld %d %d\n" , r1-1LL*dep[q[0]]*m*m , r2 , r3);
}

int main(int argc, char *argv[]) {

    cin>>n;
    for(int i=1,j,k;i<n;i++)
    {
        j = re(); k = re();
        g[j].push_back(k);
        g[k].push_back(j);
    }

    dfs(1);
    dfs(1, 1);
//  pre();

    int q;
    cin>>q;

    while(q--)
    {
        m = re();
        for(int i=1;i<=m;i++) w[i] = re() , book[w[i]] = 1;

        solve();

        for(int i=1;i<=m;i++) book[w[i]] = 0;
    }

    return 0;
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值