2019 ICPC 南昌 Regional K. Tree(树上启发式合并 + 动态开点线段树)

本文介绍了如何使用动态开点线段树解决数据结构中的查询和更新问题,特别是针对需要查找节点深度小于等于指定值的节点个数的场景。通过动态建立线段树节点,实现了在log时间复杂度内的插入、删除和查找操作,避免了内存不足的问题。代码示例展示了动态开点线段树的实现细节,包括查询和更新函数。
部署运行你感兴趣的模型镜像

思路

  1. 会dsu on tree 的都不难看出这个是个dsu ,但是这道题需要查找结点深度小于等于指定值的结点个数,每次查找插入删除最多都为log。

  2. 显然用map插入删除可以做到log,但是查找不行,所以可以考虑用线段树,但是对每个权值都开一个线段树,内存肯定不够,所以这里就有了动态开点线段树。

  3. 动态开点线段树,原理实际上就是不像普通线段树那样一建树就把整棵树都建好,而是当第一次给某个这个区间插入值的时候,才给对应区间分配结点,这样左右子树的下标关系就不再是i << 1 和 i << 1 | 1 的关系了,所以需要用数组存储结点左右儿子位置。

动态开点线段树学习推荐

细节看代码

#include<bits/stdc++.h>
using namespace std;
#define IOS std::ios_base::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);// 快读

const int N = 1e5 + 10;
const int MAX = 2e7;
int h[N],to[N << 1],ne[N << 1];
int son[N],sz[N];

int cnt = 0,node = 0,fanode,k,n;
long long res = 0;

int len[N],v[N];
int lc[MAX],rc[MAX],sum[MAX];// lc结点的左儿子的下标  rc结点右儿子的下标, sum 结点的和
// 一定要保留下标0,不用 ,但vi范围为 0 ~ n,所以建根要从1 ~ n + 1

int query(int l,int r,int rt,int L,int R){// L R 为要查询区间, l,r为目前所在区间 rt  为sum的下标
    if(rt == 0)//没有该区间
        return 0;
    
    if(L <= l && r <= R)
        return sum[rt];
    int m = (l + r) >> 1;
    int val = 0;
    if(L <= m)
        val += query(l,m,lc[rt],L,R);
    if(m + 1 <= R)
        val += query(m + 1,r,rc[rt],L,R);
    return val;
}

void update(int l,int r,int &rt,int id,int val){// rt为引用为的是儿子如果为空则更新
    if(!rt) rt = ++node;// 为0 也就是为空,才建立结点
    
    //cout << l << " " << r << " " << id << ' ' << val << " " << rt << '\n';
    if(l == r){
        sum[rt] += val;
        return ;
    }
    
    int m = (l + r) >> 1;
    if(m >= id)
        update(l,m,lc[rt],id,val);
    else update(m + 1,r,rc[rt],id,val);
    sum[rt] = sum[lc[rt]] + sum[rc[rt]];
}

void add(int x,int y){
    to[++cnt] = y;
    ne[cnt] = h[x];
    h[x] = cnt;
}

void dfs(int u,int dp){//暴力求重子树
    len[u] = dp;
    sz[u] = 1;
   
    for(int i = h[u]; ~i; i = ne[i]){
        int v = to[i];
       
        dfs(v,dp + 1);
        sz[u] += sz[v];
        if(sz[v] > sz[son[u]]) son[u] = v;
    }
}

void cal(int u){// 合并子树,求树的价值,根据val值可以选择消除信息

    int x = 2*v[fanode] - v[u];
    int y = k + 2*len[fanode] - len[u];
   
    if(x >= 0 && x <= n && 1 <= y)// vi在合理范围才查询
        res += query(1, n, x + 1, 1, y) * 2;
   

    for(int i = h[u]; ~i; i = ne[i]){
        int v = to[i];
        cal(v);
    }
}

void change(int u,int val){
    int x = v[u] + 1;
    if(~val)
        update(1, n, x, len[u], 1);
    else update(1,n, x, len[u], -1);
    for(int i = h[u]; ~i; i = ne[i]){
        int v = to[i];
        change(v,val);
    }

}

void get_ans(int u,int op){ //op为1表示为重子树 0为轻子树
   
    
    for(int i = h[u];~i; i = ne[i]){
        int v = to[i];
        if(v == son[u])    continue;
        get_ans(v,0);
    }
    
        
        if(son[u]) get_ans(son[u],1);//0 则无重子树
           
        fanode = u;
        
    for(int i = h[u];~i; i = ne[i]){
            int v = to[i];
            if(v == son[u])    continue;
            cal(v);//合并重子树和轻子树 
            change(v,1);
        }
        int x = v[u] + 1;
        // x = 4, len[u] = 2
        update(1,n,x,len[u],1);

        if(!op) 
        {       
            change(u,-1);
        }
}


int main(){
    IOS
    memset(h,-1,sizeof h);
    cin >> n >> k;

    for(int i = 1; i <= n; ++i){
       cin >> v[i];
    }

    for(int i = 1; i < n; ++i){
       int x;   cin >> x;
       add(x,i + 1);
    }
    for(int i = 1 ; i <= n + 1; ++i)// vi 0 ~ n 所以建立 1 ~ n + 1分别为对应的根
    {
        int x = 0;
        update(1, 1, x,0,0);// l r, root, id ,val
        //cout << "----------" << '\n';
    }
    //cout << "----------" << '\n';
    dfs(1,1); get_ans(1,1);
    
    
    cout << res;
    return 0;
}

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

### 2019 ICPC亚洲银川区域B题相关信息与解法 根据提供的引用内容和问题,以下是关于2019ICPC亚洲银川区域B题的相关信息与解法。 #### 题目背景与目标 在2019ICPC亚洲银川区域中,B题被描述为涉及分段状态转移的动态规划问题。题目要求选手通过合理的状态设计和状态转移方程,计算出满足特定条件的最优解。具体来说,该问题的核心在于如何通过状态压缩和动态规划来优化时间复杂度[^3]。 #### 动态规划状态设计 为了求解此问题,可以定义一个三维动态规划数组 `dp[i][j][sta]`,其中: - `i` 表示当前处理到第 `i` 个元素。 - `j` 表示当前已经分成了 `j` 段。 - `sta` 表示当前最右端的状态,取值范围为 `{0, 1, 2}`,分别表示以下三种情况: - `sta == 0`:当前所有段都已经达到最优状态,即已经计算了最大值和最小值的贡献。 - `sta == 1`:当前段已经记录了最小值,但尚未找到最大值。 - `sta == 2`:当前段已经记录了最大值,但尚未找到最小值。 #### 状态转移方程 基于上述状态设计,状态转移方程可以分为以下几种情况: 1. 如果当前段的状态为 `sta == 0`,则可以选择开始一段新的区间或继续当前段: ```cpp dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j-1][0]); ``` 2. 如果当前段的状态为 `sta == 1`,则需要继续寻找最大值: ```cpp dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j][2] + (a[i] - min_val)); ``` 3. 如果当前段的状态为 `sta == 2`,则需要继续寻找最小值: ```cpp dp[i][j][2] = max(dp[i-1][j][2], dp[i-1][j][1] + (max_val - a[i])); ``` #### 边界条件与初始化 - 初始状态设置为 `dp[0][0][0] = 0`,其余状态均初始化为负无穷大。 - 最终答案可以通过遍历所有可能的段数 `j` 和状态 `sta` 得到: ```cpp int result = 0; for(int j = 1; j <= k; ++j){ result = max(result, dp[n][j][0]); } ``` #### 示例代码实现 以下是一个简单的代码框架,用于实现上述动态规划算法: ```cpp #include <bits/stdc++.h> using namespace std; const int INF = 1e9; int dp[5010][510][3]; // i: 当前元素, j: 分段数, sta: 状态 int a[5010]; // 输入数组 int main(){ int n, k; cin >> n >> k; for(int i = 1; i <= n; ++i) cin >> a[i]; // 初始化 memset(dp, -0x3f, sizeof(dp)); dp[0][0][0] = 0; // 动态规划转移 for(int i = 1; i <= n; ++i){ for(int j = 0; j <= k; ++j){ for(int sta = 0; sta <= 2; ++sta){ if(dp[i-1][j][sta] == -INF) continue; if(sta == 0){ dp[i][j][0] = max(dp[i][j][0], dp[i-1][j][0]); if(j > 0) dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-1][0] + a[i]); } else if(sta == 1){ dp[i][j][1] = max(dp[i][j][1], dp[i-1][j][1]); dp[i][j][2] = max(dp[i][j][2], dp[i-1][j][1] + a[i]); } else if(sta == 2){ dp[i][j][2] = max(dp[i][j][2], dp[i-1][j][2]); } } } } // 计算最终结果 int result = -INF; for(int j = 1; j <= k; ++j){ result = max(result, dp[n][j][0]); } cout << result << endl; return 0; } ``` #### 复杂度分析 - 时间复杂度:由于状态数量为 `O(n * k * 3)`,每次状态转移的时间复杂度为常数,因此总时间复杂度为 `O(n * k)`。 - 空间复杂度:使用了一个三维数组存储状态,空间复杂度为 `O(n * k * 3)`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值