校门外的树——树状数组的简单应用
作者:互联网
\(\color{blue}{题目}\)
\(传送门:\) https://vijos.org/p/1448
\(\color{blue}{分析}\)
\(\color{blue}{基础}\)
总而言之,不想敲线段树
好了,回归正题,首先我们用大暴力思维考虑一下,如果我们每次种树都给其所在区间填上种树的id,之后遍历找上所需区间的不同的树的id是不是就可以知道能见到多少种树了呢?
确实如此,当然这无疑是通不过的。我们反过来想:如果按上述做法我们是不是不需要每次种树的种类不同、种树时是按连续区间来种这两个条件呢?所以说,正确的做法就应该是利用上述的两个条件来做。
Q1:种树时是按连续区间来种意味着什么呢?
肉眼可见地意味着如果出现\([a,b,c,d,e]\)这样一个区间(字母代表每个点的树的数量)那么\(a,e\)最多只会有\(min(b,c,d)\)种相同的树种
Q1:每次种树的种类不同意味着什么呢?
意味着没有两次会种相同的树种,意味着如果一个区间出现\([1,0,3,0,1]\)(字母代表每个点的树的数量),那我们就知道这个区间种了5种树(这种情况我们就无需知道树的id了)
\(\color{blue}{进阶}\)
因此我们的目标就转化为在解题的过程种消去树的id这一不必记录的变量。
之后我们考虑如果我们知道\([l,r1],[l,r2](r1<r2)\),我们可不可以知道\([r1+1,r2]\)呢?我们是不可以知道的。但这是一种思维(只考虑区间本身而不特意关注区间内的每个点)
这时我们引进树状数组的概念:
整理一下:
\(C[2*i-1] = A[2*i-1],C[0] = 0\)
\(C[2*i] = A[2*i]+A[2*i-1]+C[2*(i-1)]\)
显然数组C和数组A是相互等价的(由一个可以推出来另一个)
那我们继续考虑区间问题,比如我们要求区间内树的个数而非树的种类,那我们是不是很快就可以算完了?
比如我要求[4,7]的树的个数,我只需要找到[1,3]的树的个数和[1,7]的树的个数
然后[1,3]的树的个数就等于C[4]-A[4],[1,7]的树的个数就等于C[8]-A[8]
但是现在问题不同,树的种类是一个相较而言更加棘手的问题,这时我们需要用到一些小的技巧。
首先区间只有其两个端点决定,我们为每一个端点建一个C(下面代码里是sum)如果要在端点a种树,让包含A[a]的C加一
比如我们在[4,6]种树,对于4(左端点),我们让c1[4]、c1[8]、...都自增,
对于6(右端点),我们让c2[6]、c2[8]、....都自增,
这样我们找5时用式子
{c1[5]+c1[4](去掉lowbit)}-{c2[5]+c2[4](去掉lowbit)}
就可以找到。
证明可以通过数归来证,博主也尚未想到更好的办法,只能感慨人与人之间的差距罢了,像我这样只能努力采集他们的智慧来提升自己了吧。
以下为AC code:
#include<bits/stdc++.h>
using namespace std;
#define INIT ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
#define N 50005
int n,m;
int sum1[N];
int sum2[N];
int lowbit(int x){
return x&(-x);
}
void add(int pos,int flag){
int *t=sum1;
if(flag==2) t = sum2;
while(pos<=n){
t[pos]++;
pos+=lowbit(pos);
}
}
int query(int pos,int flag){
int sum =0;
int *t = sum1;
if(flag==2) t = sum2;
while(pos){
sum+=t[pos];
pos-=lowbit(pos);
}
return sum;
}
int main(){
INIT;
//freopen("in.txt","r",stdin);
cin>>n>>m;
for(int i=1;i<=m;i++){
int f;
cin>>f;
int l,r;
cin>>l>>r;
if(f==1){
add(l,1);
add(r,2);
}else{
cout<<query(r,1)-query(l-1,2)<<endl;
}
}
return 0;
}
标签:端点,树状,int,个数,门外,种树,数组,区间,我们 来源: https://www.cnblogs.com/OceanCT/p/15847616.html