Distance on the tree-南昌邀请赛网络赛 【树链剖分】【主席树】【lca】

题目链接:https://nanti.jisuanke.com/t/38229

题目大意:

给一颗树,m次查询ui->vi这条链中边权小于等于ki的边数。

做法一:树链剖分+主席树(这里并不需要进行离散化,但是比赛的时候忘了)

#include <bits/stdc++.h>
#define mid (l+r>>1)
#define lson (o<<1)
#define rson (o<<1|1)
#define all(x) (x).begin(),(x).end()
using namespace std;
const int N = 2e5+1000;
vector<int>nxt[N],len[N];
int fa[N],deep[N],id[N],siz[N],son[N],tot,s[N],top[N],ss[N];
int n,m;
int SIZ;

struct TREE{
    int l,r,x;
}tree[N*20];
int rk[N],Tot,rt[N];
void insert(int &o,int l,int r,int data){
    tree[++Tot] = tree[o];
    o = Tot;
    tree[o].x++;
    if(l==r) return ;
    if(mid>=data) insert(tree[o].l,l,mid,data);
    else insert(tree[o].r,mid+1,r,data);
}
int query(int lo,int ro,int l,int r,int h,int t){
    if(l>=h&&r<=t) return tree[ro].x-tree[lo].x;
    int ans = 0;
    if(mid>=h) ans += query(tree[lo].l,tree[ro].l,l,mid,h,t);
    if(mid<t) ans += query(tree[lo].r,tree[ro].r,mid+1,r,h,t);
    return ans;
}

void dfs(int u,int f) {
    deep[u] = deep[f]+1;
    son[u] = 0;
    siz[u] = 1;
    fa[u] = f;
    for(int i = 0; i < nxt[u].size(); i++) {
        int v = nxt[u][i];
        int w = len[u][i];
        if(v==f) continue;
        s[v] = w;
        dfs(v,u);
        siz[u] += siz[v];
        if(siz[son[u]]<siz[v]) son[u] = v;
    }
}
void dfs2(int u,int t) {
    id[u] = ++tot;
    ss[id[u]] = s[u];
    top[u] = t;
    if(son[u]) dfs2(son[u],t);
    for(auto v:nxt[u]) {
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
int Query(int u,int v,int x) {
    int tu = top[u];
    int tv = top[v];
    int ans = 0;
    while(tu!=tv) {
        if(deep[tu]<deep[tv]) {
            swap(u,v);
            swap(tu,tv);
        }
        ans += query(rt[id[tu]-1],rt[id[u]],1,SIZ,1,x);
        u = fa[tu];
        tu = top[u];
    }
    if(u==v) return ans;
    if(id[u]>id[v]) swap(u,v);
    ans += query(rt[id[u]],rt[id[v]],1,SIZ,1,x);
    return ans;
}

struct Q{
    int u, v, w;
}Q[N];
vector<int> k;
int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i < n; i++) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        nxt[u].push_back(v);
        nxt[v].push_back(u);
        len[u].push_back(w);
        len[v].push_back(w);
        k.push_back(w);
    }
    for(int i = 1; i <= m; i++) {
        int u, v, x;
        scanf("%d%d%d",&Q[i].u,&Q[i].v,&Q[i].w);
        k.push_back(Q[i].w);
    }
    sort(all(k));
    k.erase(unique(all(k)),k.end());
    SIZ = k.size()+10;
    dfs(1,0);
    dfs2(1,1);
    rt[0] = 0;
    tree[0].l = tree[0].r = tree[0].x = 0;
    for(int i = 1; i <= n; i++) {
        rt[i] = rt[i-1];
        int x = lower_bound(all(k),ss[i])-k.begin()+1;
        insert(rt[i],1,SIZ,x);
    }
    for(int i = 1; i <= m; i++) {
        int x = lower_bound(all(k),Q[i].w)-k.begin()+1;
        printf("%d\n",Query(Q[i].u,Q[i].v,x));
    }
    return 0;
}

-----------------------------------------------------------------更新线--------------------2019.8.3----------------------------------------------

因为看到评论有人问离散化,所以在这里稍微解释一下为什么这题不需要离散化。

首先我们要清楚主席树的空间复杂度,在一般情况下主席树是一颗可持久化的权值线段树,

假如给一个长度为N的数组,数组元素的大小在[1,MAX]之间,你需要查询 [L,R] 内小于等于K的元素有多少个。

 

显然主席树会执行N次insert操作,最坏情况下每一次insert操作会开辟一条新的链。

我们考虑这条链的长度,如果我们不离散化的话,主席树的根节点所维护的左右端点就是[1,MAX],这条链延伸log(MAX)的长度就到了叶子节点,因为每向下一层,所维护的区间长度就会减少一半。

主席树的空间复杂度为O(N*log(MAX)), 单次查询的时间复杂度为O(log(MAX))

 

假如我们进行了离散化,那么离散化后的数值大小将在[1,N]以内,即离散化后MAX = N。

依然按上面的步骤分析复杂度,得主席树的空间复杂度为O(N*log(N)), 单次查询的时间复杂度为O(log(N))

而在本题中log(N) = log(1e5) <= 20 , log(MAX) = log(1e9) <= 32 , 因为都是log级别所以两者相差不大,所以我们并不需要离散化。

下面附上没有离散化AC的代码

#include <bits/stdc++.h>
#define mid (l+r>>1)
#define lson (o<<1)
#define rson (o<<1|1)
#define all(x) (x).begin(),(x).end()
using namespace std;
const int N = 2e5+1000;
vector<int>nxt[N],len[N];
int fa[N],deep[N],id[N],siz[N],son[N],tot,s[N],top[N],ss[N];
int n,m;
int SIZ;
 
struct TREE{
    int l,r,x;
}tree[N*40];
int rk[N],Tot,rt[N];
void insert(int &o,int l,int r,int data){
    tree[++Tot] = tree[o];
    o = Tot;
    tree[o].x++;
    if(l==r) return ;
    if(mid>=data) insert(tree[o].l,l,mid,data);
    else insert(tree[o].r,mid+1,r,data);
}
int query(int lo,int ro,int l,int r,int h,int t){
    if(l>=h&&r<=t) return tree[ro].x-tree[lo].x;
    int ans = 0;
    if(mid>=h) ans += query(tree[lo].l,tree[ro].l,l,mid,h,t);
    if(mid<t) ans += query(tree[lo].r,tree[ro].r,mid+1,r,h,t);
    return ans;
}
 
void dfs(int u,int f) {
    deep[u] = deep[f]+1;
    son[u] = 0;
    siz[u] = 1;
    fa[u] = f;
    for(int i = 0; i < nxt[u].size(); i++) {
        int v = nxt[u][i];
        int w = len[u][i];
        if(v==f) continue;
        s[v] = w;
        dfs(v,u);
        siz[u] += siz[v];
        if(siz[son[u]]<siz[v]) son[u] = v;
    }
}
void dfs2(int u,int t) {
    id[u] = ++tot;
    ss[id[u]] = s[u];
    top[u] = t;
    if(son[u]) dfs2(son[u],t);
    for(auto v:nxt[u]) {
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
int Query(int u,int v,int x) {
    int tu = top[u];
    int tv = top[v];
    int ans = 0;
    while(tu!=tv) {
        if(deep[tu]<deep[tv]) {
            swap(u,v);
            swap(tu,tv);
        }
        ans += query(rt[id[tu]-1],rt[id[u]],1,SIZ,1,x);
        u = fa[tu];
        tu = top[u];
    }
    if(u==v) return ans;
    if(id[u]>id[v]) swap(u,v);
    ans += query(rt[id[u]],rt[id[v]],1,SIZ,1,x);
    return ans;
}
 
struct Q{
    int u, v, w;
}Q[N];
vector<int> k;
int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i < n; i++) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w); //这里我让权值全部加了1,因为我的旧代码里的左端点是从1开始的,就不再修改了。
        w++;                                       
        nxt[u].push_back(v);
        nxt[v].push_back(u);
        len[u].push_back(w);
        len[v].push_back(w);
    }
    for(int i = 1; i <= m; i++) {
        int u, v, x;
        scanf("%d%d%d",&Q[i].u,&Q[i].v,&Q[i].w);
        Q[i].w++;
    }
    SIZ = 1e9+10;   //右端点直接设为最大值
    dfs(1,0);
    dfs2(1,1);
    rt[0] = 0;
    tree[0].l = tree[0].r = tree[0].x = 0;
    for(int i = 1; i <= n; i++) {
        rt[i] = rt[i-1];
        insert(rt[i],1,SIZ,ss[i]);
    }
    for(int i = 1; i <= m; i++) 
        printf("%d\n",Query(Q[i].u,Q[i].v,Q[i].w));
    return 0;
}

--------------------------------------更新结束线---------------------------------2019.8.3------------------------------------------

做法二:按照dfs顺序在树上直接建主席树

#include <bits/stdc++.h>
#define mid (l+r>>1)
#define lson (o<<1)
#define rson (o<<1|1)
#define all(x) (x).begin(),(x).end()
using namespace std;
const int N = 2e5+1000;
vector<int>nxt[N],len[N],k;
int fa[N][20],deep[N],tot;
int n,m;
int SIZ=1e9;

struct TREE{
    int l,r,x;
}tree[N*20];
int rk[N],Tot,rt[N];
void insert(int &o,int l,int r,int data){
    tree[++Tot] = tree[o];
    o = Tot;
    tree[o].x++;
    if(l==r) return ;
    if(mid>=data) insert(tree[o].l,l,mid,data);
    else insert(tree[o].r,mid+1,r,data);
}
int query(int lo,int ro,int l,int r,int h,int t){
    if(l>=h&&r<=t) return tree[ro].x-tree[lo].x;
    int ans = 0;
    if(mid>=h) ans += query(tree[lo].l,tree[ro].l,l,mid,h,t);
    if(mid<t) ans += query(tree[lo].r,tree[ro].r,mid+1,r,h,t);
    return ans;
}
void dfs(int u, int f, int d) {
    deep[u] = d;
    fa[u][0] = f;
    int siz = nxt[u].size();
    for(int i = 0; i < siz; i++) {
        int v = nxt[u][i];
        int w = len[u][i];
        if(v == f) continue;
        rt[v] = rt[u];
        insert(rt[v],0,SIZ,w);
        dfs(v, u, d+1);
    }
}
void init(int n) {
    for(int j = 1; j <= 19; j++)
        for(int i = 1; i <= n; i++)
            fa[i][j] = fa[fa[i][j-1]][j-1];
}
int lca(int u, int v){
    if(deep[u] < deep[v]) swap(u, v);
    int diff = deep[u] - deep[v];
    for(int i = 19; i >= 0; i--)
        if(diff>>i&1)
            u = fa[u][i];
    if(u == v) return u;
    for(int i = 19; i >= 0; i--)
        if(fa[u][i] != fa[v][i]) {
            u = fa[u][i];
            v = fa[v][i];
        }
    return fa[u][0];
}

int Query(int u,int v,int x) {
    int uv = lca(u,v);
    return query(rt[uv],rt[u],0,SIZ,0,x) + query(rt[uv],rt[v],0,SIZ,0,x);
}
struct Q{
    int u, v, w;
}Q[N];
int main() {
    scanf("%d%d",&n,&m);
    for(int i = 1; i < n; i++) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        nxt[u].push_back(v);
        nxt[v].push_back(u);
        len[u].push_back(w);
        len[v].push_back(w);
    }
    rt[0] = 0;
    tree[0].l = tree[0].r = tree[0].x = 0;
    dfs(1,0,1);
    init(n);
    for(int i = 1; i <= m; i++) {
        int u, v, x;
        scanf("%d%d%d",&u,&v,&x);
        printf("%d\n",Query(u,v,x));
    }
    return 0;
}

做法三:离线,树链剖分,排序加边,树状数组维护。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值