我们给出一个具有N个元素和N个范围的数组A,每个形式为[L,R]。将一个范围的值称为A中从索引L到索引R的所有元素的总和。
示例:令数组A = [2 5 7 9 8]且给定的范围是[2,4],则此范围的值为5 + 7 + 9 = 21
现在我们给出Q查询两种类型之一的每个查询:
1. 0 X Y : It means change Xth element of array to Y.
2. 1 A B : It means we need to report the sum of values of ranges from A to B.
示例:让数组A = [2 3 7 8 6 5]并让我们有3个范围:
R1: [1,3] Then value corresponding to this range is 2+3+7=12
R2: [4,5] Then value corresponding to this range is 8+6=14
R3: [3,6] Then value corresponding to this range is 7+8+6+5=26
现在让我们有3个查询:
Q1: 1 1 2
Then here answer is value of Range1 + value of Range2 = 12+14=26
Q2: 0 2 5
It means Change 2nd element to 5 from 3.It will change the result of Range 1.
Now value of Range1 becomes 2+5+7=14
Q3: 1 1 2
Then here answer is value of Range1 + value of Range2 = 14+14=28
如果我们有10 ^ 5个查询并且N也高达10 ^ 5,该如何做到这一点。如何以有效的方式向Queries2报告?
我的方法:第一个查询可以轻松处理。我可以从数组中构建一个分段树。我可以用它来计算第一个数组中间隔的总和(第二个数组中的一个元素)。但是如何处理O(log n)中的第二个查询?在最坏的情况下,我更新的元素将在第二个数组的所有间隔中。
我需要O(Qlog N)或O(Q(logN)^ 2)解决方案。
显然我们不能为每个查询提供O(N)。所以请帮助我们获得有效的方法
我当前的代码:
#include<bits/stdc++.h>
using namespace std;
long long arr[100002],i,n,Li[100002],Ri[100002],q,j;
long long queries[100002][2],query_val[100002],F[100002],temp;
long long ans[100002];
int main()
{
scanf("%lld",&n);
for(i=1;i<=n;i++)
scanf("%lld",&arr[i]);
for(i=1;i<=n;i++)
{
scanf("%lld%lld",&Li[i],&Ri[i]);
}
for(i=1;i<=n;i++)
{
F[n] = 0;
ans[i] = 0;
}
scanf("%lld",&q);
for(i=1;i<=q;i++)
{
scanf("%lld",&query_val[i]);
scanf("%lld%lld",&queries[i][0],&queries[i][1]);
}
for(i=1;i<=n;i++)
{
for(j=Li[i];j<=Ri[i];j++)
{
F[i] = F[i] + arr[j];
}
}
long long diff;
long long ans_count = 0,k=1;
for(i=1;i<=q;i++)
{
if(query_val[i] == 1)
{
temp = arr[queries[i][0]];
arr[queries[i][0]] = queries[i][1];
diff = arr[queries[i][0]] - temp;
for(j=1;j<=n;j++)
{
if(queries[i][0]>=Li[j] && queries[i][0]<=Ri[j])
F[j] = F[j] + diff;
++k;
}
}
else if(query_val[i] == 2)
{
++ans_count;
for(j=queries[i][0];j<=queries[i][1];j++)
ans[ans_count] = ans[ans_count] + F[j];
}
}
for(i=1;i<=ans_count;i++)
{
printf("%lld\n",ans[i]);
}
return 0;
}
虽然代码是正确的但是对于更大的测试用例需要很长时间。请帮助
答案 0 :(得分:1)
#include <iostream>
using namespace std;
typedef long long ll;
void updateSegementLazyTree(ll *tree , ll *lazy , ll low, ll high,ll startR ,ll endR ,ll updateValue ,ll treeNode)
{
//before using current node we need to check weather currentnode has any painding updation or not
if (lazy[treeNode]!=0)
{
//update painding updation
tree[treeNode] += (high-low+1)*lazy[treeNode];
//transfer update record to child of current node if child possible
if (low!=high)
{
//that's means child possible
lazy[treeNode*2] += lazy[treeNode]; //append update to left child
lazy[treeNode*2+1] += lazy[treeNode]; //append update to right child
}
lazy[treeNode]=0;//remove lazyness of current node
}
//if our current interval [low,high] is completely outside of the given Interval[startR,endR]
if (startR >high || endR <low || low>high)
{
//then we have to ignore those path of tree
return;
}
//if our current interval is completely inside of given interval
if (low >=startR && high <=endR)
{
//first need to update the current node with their painding updation
tree[treeNode] += (high-low+1)*updateValue;
if (low!=high)
{
//that's means we are at the non-leaf node
lazy[treeNode*2] +=updateValue; //so append lazyness to their left child
lazy[treeNode*2+1] +=updateValue;//append lazyness to their right child
}
return;
}
//partially inside and outside then we have to traverse all sub tree i.e. right subtree and left subtree also
ll mid=(low+high)/2;
updateSegementLazyTree(tree , lazy , low, mid, startR , endR , updateValue , treeNode*2);
updateSegementLazyTree(tree , lazy , mid+1, high, startR , endR , updateValue , treeNode*2+1);
//while poping the function from stack ,we are going to save what i have done....Ok!!!!
//update tree node:-
tree[treeNode] = tree[treeNode*2] + tree[treeNode*2+1]; //left sum+rightsum(after updation)
}
ll getAnswer(ll *tree ,ll * lazy , ll low, ll high ,ll startR,ll endR , ll treeNode)
{
//base case
if (low>high)
{
return 0;
}
//completely outside
if (low >endR || high <startR)
{
return 0;
}
//before using current node we need to check weather currentnode has any painding updation or not
if (lazy[treeNode]!=0)
{
//i.e. if we would have added x value from low to high then total changes for root node will be (high-low+1)*x
tree[treeNode] += (high-low+1)*lazy[treeNode];
if (low!=high)
{
//if we are at non-leaf node
lazy[treeNode*2] += lazy[treeNode]; //append updateion process to left tree
lazy[treeNode*2+1] += lazy[treeNode];//append updation process to right tree
}
lazy[treeNode]=0;
}
//if our current interval is completely inside of given interval
if (low >=startR && high <=endR)
{
return tree[treeNode];
}
//if our current interval is cpartially inside and partially out side of given interval then we need to travers both side left and right too
ll mid=(low+high)/2;
if(startR>mid)
{
//that's means our start is away from mid so we need to treverse in right subtree
return getAnswer( tree , lazy , mid+1, high, startR, endR , treeNode*2+1);
}else if(endR <= mid){
//that's means our end is so far to mid or equal so need to travers in left subtree
return getAnswer( tree , lazy , low, mid, startR, endR , treeNode*2);
}
ll left=getAnswer( tree , lazy , low, mid, startR, endR , treeNode*2); //traverse right
ll right=getAnswer( tree , lazy , mid+1, high, startR, endR , treeNode*2+1); //and left
return (left+right);//for any node total sum=(leftTreeSum+rightTreeSum)
}
int main()
{
int nTestCase;
cin>>nTestCase;
while(nTestCase--)
{
ll n,nQuery;
cin>>n>>nQuery;
ll *tree=new ll[3*n]();
ll *lazy=new ll[3*n]();
while(nQuery--)
{
int choice;
cin>>choice;
if (choice==0)
{
ll startR,endR,updateValue;
cin>>startR>>endR>>updateValue;
//0:- start index , n-1 end index ,1 treeIndex tree is our segment tree and lazy is our lazy segment tree
updateSegementLazyTree(tree , lazy , 0, n-1, startR-1 , endR-1 , updateValue , 1);
// for (int i = 0; i < 3*n; ++i)
// {
// cout<<i<<"\t"<<tree[i]<<"\t"<<lazy[i]<<endl;
// }
}else{
ll startR,endR;
cin>>startR>>endR;
ll answer=getAnswer(tree , lazy , 0, n-1 , startR-1 , endR-1 , 1);
cout<<answer<<endl;
}
}
}
}
updateSegementLazyTree()
采用两个大小为4*n
的数组,因为长度为log(n)
的可能节点总数为2*2^log(n)
,最大为4*n
。然后,我们还需要interval[startR,endr]
和updateValue
并通过对冲来维持。 Tree[treeNode]
代表左右所有元素的总和。
样本输入如下:
1
8 6
0 2 4 26
0 4 8 80
0 4 5 20
1 8 8
0 5 7 14
1 4 8
因此,对于第一个查询,我们必须将2-4
更新为+26。无需更新2-4之间的所有元素,我们将其存储在惰性树中,每当我们访问树中的任何节点时,我们首先检查天气该节点是否有任何待处理的更新。如果没有任何待处理的更新,请完成并将其转移给他们的孩子。
q1:- 0 2 4 26
tree[0,78,78,0,26,52,0,0,0,26]
尝试建立树索引;用于左tree(2*i+1)
和right(2*i+1)
第一索引至少为78,即树的顶部,因此从[0,n-1]
开始,当前最大值为78。
tree[treeNode] += (high-low+1)*lazy[treeNode];
如果我们将x
从低索引添加到高索引,则由于从i
进行索引,因此在整个子数组(high-low+1)*x ; -1
中添加了0
。
然后,在从树中访问任何节点之前,我们懒惰地检查天气该节点是否有任何待处理的更新。 if (lazy[treeNode]!=0)
(如果有),然后更新并转移懒惰给他们的孩子。继续对左子树和右子树进行操作。
然后我们在getAnswer()
内到达range[startR,endR]
如前所述,我们首先检查每个受影响节点的挂起更新。如果为true,则完成更新并根据时间间隔递归地调用左右子树。
最后,我们得到根节点的leftSubtree
和rightsubtree
的总和,将它们相加并返回。
时间复杂度
在最新的getAnswer()
中,最坏的情况是必须遍历整个树,即树O(2*Log N)
的高度。 2*log n
,因为这是最坏的情况,我们必须在左右子树中旅行,例如间隔[0-n-1]
。
对于k
查询,整体时间复杂度为O(K*log n)
。
答案 1 :(得分:0)
您可以使用细分树。 http://www.geeksforgeeks.org/segment-tree-set-1-sum-of-given-range/
#include <cstdio>
const int MAX_N = 1000003;
int tree[(1 << 21) + 3], a[MAX_N], N, M;
int x, y;
int query(int n, int l, int r) {
if(l > y || r < x) {
return 0;
}
if(l >= x && r <= y) {
return tree[n];
}
int q = query(2*n, l, (l + r)/2);
int p = query(2*n + 1, (l + r)/2 + 1, r);
return p + q;
}
void init(int n, int l, int r) {
if(l == r) {
tree[n] = a[l];
return;
}
init(2*n, l, (l + r)/2);
init(2*n + 1, (l + r)/2 + 1, r);
tree[n] = tree[2*n] + tree[2*n + 1];
}
void update(int n, int l, int r) {
if(l > y || r < x)
return;
if(l == r && x == r) {
tree[n] = y;
return;
}
else if(l == r) {
return;
}
update(2*n, l, (l + r)/2);
update(2*n + 1, (l + r)/2 + 1, r);
tree[n] = tree[2*n] + tree[2*n + 1];
}
int main() {
scanf("%d%d", &N, &M);
for(int i = 0; i < N; i++) {
scanf("%d", &a[i]);
}
init(1, 0, N - 1);
for(int i = 0; i < M; i++) {
int c;
scanf("%d%d%d", &c, &x, &y);
if(!c) {
a[x] = y;
update(1, 0, N - 1);
} else {
printf("%d\n", query(1, 0, N - 1));
}
}
return 0;
}
基本思想是将元素数组扩展为二叉树。该树的每个节点都包含有关其子元素总和的信息。您可以通过应用此技巧轻松了解某个节点覆盖的范围:
Root保存范围[1,N]
的信息。
root的左子节点包含有关范围[1, int(N/2)]
的信息。
root的右子节点包含有关范围[int(N/2)+1, N]
的信息。
一般情况下,节点&#39; A&#39;正在保存有关范围[l, r]
的信息。然后离开了孩子
保存有关范围[l, int((l+r)/2)]
的信息,并且正确的孩子持有信息
关于范围[int((l+r)/2)+1, r]
。
在数组中表示二叉树也有很好的技巧。
让我们说你把你的树放在阵列的树上。 (因为我在做我的代码)。然后是那根
树将在tree[1]
。根的左子将是tree[2]
和树的右子
root将是tree[3]
。
一般情况下,如果您在节点n
上,那么其左侧孩子为2*n
,右侧孩子为2*n + 1
。
这就是为什么我用{1,0,N-1)调用我的query
和update
函数。我从根节点1
开始。我用该节点i [0,N-1]覆盖的范围。我总是试图找到第一个节点,它适合我计算总和的范围。
这是一个开始。尝试使用Google搜索更多有关细分树的信息。当您开始探索时,您将看到有几种方法可以代表您的树。
祝你好运。 :)