SDNUOJ 1665-1668(树状数组的应用)

本文详细介绍了树形数组的概念、操作原理及其在区间查询和单点修改问题中的应用。通过实例代码展示了如何实现树形数组的add和ask操作,并提供了两种不同的构建方法。此外,还提及了树形数组在区间修改和区间查询问题中的解决方案,以及其与线段树的比较。
摘要由CSDN通过智能技术生成

这篇文章写的很详细:
链接
这个b站上的视频讲的也很透彻:
链接
什么是树形数组:
在这里插入图片描述
树形数组是一种用来维护前缀和的工具,上图中序列上的就是树形数组,它虽然长得像个树,但是确是一个数组,比如上面的t[4]的值就是1-4的前缀和,其他元素以此类推。

add操作实现原理:
在这里插入图片描述
ask操作实现原理:
在这里插入图片描述
下面是区间查询+单点修改(1665and1666)的代码:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数 
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组 
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
	ll ans=0;
	for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层 
	{
		ans+=tree[j];	
	}
	return ans;
}//ask操作,求1-r的前缀和 
void add(ll p,ll w)
{
	for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作 
	{
		tree[j]+=w;
	}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树形数组上是让含有dat[p]的项都加w 
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		dat[i]=read();
	}
	for(int i=1;i<=n;++i)
	{
		ll k=lowbit(i);
		for(int j=1;j<=k;++j)
		{
			tree[i]+=dat[i-k+j];//树状数组储存 
		}
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		p_l=read();
		w_r=read();
		switch(op)
		{
			case 1:
				{
					write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
					printf("\n");
					break;
				}
			case 2:
				{
					dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值 
					add(p_l,w_r);
					break;
				}
		}
	}
	return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/

下面是另一种构建树状数组的方法(这种比较好,上面那种是我自己按照意思写的):

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数 
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组 
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
	ll ans=0;
	for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层 
	{
		ans+=tree[j];	
	}
	return ans;
}//ask操作,求1-r的前缀和 
void add(ll p,ll w)
{
	for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作 
	{
		tree[j]+=w;
	}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树状数组上是让含有dat[p]的项都加w 
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		dat[i]=read();
		add(i,dat[i]);//树状数组储存 
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		p_l=read();
		w_r=read();
		switch(op)
		{
			case 1:
				{
					write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
					printf("\n");
					break;
				}
			case 2:
				{
					dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值 
					add(p_l,w_r);
					break;
				}
		}
	}
	return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/

下面是区间修改+区间查询(1667and1668)的代码:
听说这种题用线段树比较好解= =,我用树状数组也就是套了个公式(蒟蒻瑟瑟发抖)。
在这里插入图片描述
其中tree1维护了差分数组b[i]的前缀和,tree2维护了i*b[i]的前缀和,sum是数据数组a的前缀和。代码中没有写出差分数组b是因为可以利用树状数组不用写出而直接求其前缀和。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[1000005],sum[1000005],tree1[1000005],tree2[1000005];
ll n,q,op,l,r,w;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);
}
ll ask1(ll r)
{
	ll ans=0;
	for(int i=r;i>=1;i-=lowbit(i))
	{
		ans+=tree1[i];	
	}
	return ans;
}
ll ask2(ll r)
{
	ll ans=0;
	for(int i=r;i>=1;i-=lowbit(i))
	{
		ans+=tree2[i];
	}
	return ans;
}
void add1(ll p,ll w)
{
	for(int i=p;i<=n;i+=lowbit(i))
	{
		tree1[i]+=w;
	}
}
void add2(ll p,ll w)
{
	for(int i=p;i<=n;i+=lowbit(i))
	{
		tree2[i]+=w;
	}
}
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=read();
		if(i==1) sum[i]=a[i];
		else sum[i]=sum[i-1]+a[i];
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		switch(op)
		{
			case 1:
				{
					l=read();
					r=read();
					write((sum[r]+(r+1)*ask1(r)-ask2(r))-(sum[l-1]+l*ask1(l-1)-ask2(l-1)));
					printf("\n");
					break;
				}
			case 2:
				{
					l=read();
					r=read();
					w=read();
					add1(l,w);
					add1(r+1,-w);
					add2(l,l*w);
					add2(r+1,-(r+1)*w);
					break;
				}
		}
	}
	return 0;
} 
在 Vue3 中,`el-table` 组件通常用于展示表格数据,而处理树形数组结构的合并表格通常是通过递归和深度优先搜索(Depth First Search, DFS)的方式实现。Vue 的响应式系统使得数据变化时表格能够自动更新。 假设你有这样一个树形数组: ```javascript [ { id: 1, children: [ { id: 11, name: 'Item 11' }, { id: 12, name: 'Item 12', children: [{ id: 121, name: 'Item 121' }] } ], name: 'Root Node' }, // 更多节点... ] ``` 你可以创建一个自定义组件 `TreeNode`,结合 `v-for` 和递归来渲染每个节点及其子节点,同时处理合并行的情况。例如,当遇到相同的父级 ID 时,可以将它们合并到一行显示。 以下是一个简单的示例代码片段: ```html <template> <el-table :data="treeData" border> <el-table-column prop="name" label="Name" /> <!-- 添加其他列... --> <el-table-column show-overflow-tooltip render="renderTreeRow" ></el-table-column> </el-table> </template> <script> export default { components: { TreeNode: () => import('./TreeNode.vue'), // 引入单独处理节点的组件 }, data() { return { treeData: [ ... // 初始化你的树形数据 ] }; }, methods: { renderTreeRow(h, { row }) { const treeNode = this.TreeNode(row); return h('div', {}, [treeNode]); } } }; </script> <!-- 树节点组件TreeNode.vue --> <template> <el-row v-if="row.children.length === 0"> <!-- 展示单个节点的信息 --> </el-row> <el-row v-else> <el-col :span="4">{{ row.name }}</el-col> <!-- 处理合并行,这里只展示一个例子 --> <el-col :span="8"> <ul> <li v-for="(child, index) in row.children" :key="child.id"> <TreeNode :item="child" /> <!-- 如果当前孩子和其他孩子的某个字段相等,就合并行 --> <span v-if="index !== 0 && shouldMerge(row, child)">...</span> </li> </ul> </el-col> </el-row> </template> <script> // 在 TreeNode 组件里添加判断是否需要合并行的函数 methods: { shouldMerge(parent, child) { // 这里可以根据需求编写条件,比如检查 name、id 等属性是否相同 return parent.name === child.name; } } </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值