约定:本文的下标从1开始,代码尽量从1开始(我在努力习惯),一般使用LaTeX \LaTeX L A T E X 美化
这一节主要介绍了一些基础的数据结构 (废话) ,前缀和,差分,二分查找,离散化,ST表,线段树等。
来给大家一一介绍一下
前缀和
求解问题:
给你一个数组a a a ,n n n 次询问,每次输入l , r l,r l , r ,求∑ i = l r a [ i ] \sum\limits_{i=l}^ra[i] i = l ∑ r a [ i ] (就是a[l…r]的和) 。
基本思想:
计算数组b b b ,对于每一个b [ i ] b[i] b [ i ] ,值就是∑ j = 1 i a [ j ] \sum\limits_{j=1}^ia[j] j = 1 ∑ i a [ j ] .
对于每次的l,r询问,答案就是b [ r ] − b [ l − 1 ] b[r]-b[l-1] b [ r ] − b [ l − 1 ] .
问题来了:如何快速计算b ? b? b ?
1 2 for (int i = 1 ;i <= n;i++) b[i] = a[i] + b[i-1 ];
我们只需要将前缀和累加即可。
如果我们不需要原来的a a a 了,这样写代码可以减少内存的浪费 (别和我扯空间复杂度) ,
注意,a[0]一定要=0! \ \ \ \text{\color{red}注意,a[0]一定要=0!} 注意 ,a[0] 一定要 =0 !
1 2 for (int i = 1 ;i <= n;i++) a[i] += a[i-1 ];
差分
求解问题:
给定一个数组a a a ,n n n 次操作,每次给定l , r , v l,r,v l , r , v ,使区间a[l,r]都加上v.输出最后的数组
差分其实就是前缀和的逆运算(可以这样理解),计算差分和计算前缀和一样简单:
注意,像有损前缀和一样的,for语句要倒着写! 为啥呢?请看下面这个: a = 1 , 9 , 2 , 6 , 1 , 8 , 1 , 7 我们手玩是这样的(也就是无损的) b = 1 , 8 , − 7 , 4 , − 5 , 7 , − 7 , 6 但是如果正着减的话,就会变成这样: a = 1 , 8 , − 6 , 12 , − 12 , 20 , − 19 , 26 \\ \color{red}{\text{注意,像有损前缀和一样的,for语句要倒着写!}}\\
\color{black}
\text{为啥呢?请看下面这个:}\\
a={1,9,2,6,1,8,1,7}\\
\text{我们手玩是这样的(也就是无损的)}
b={1,8,-7,4,-5,7,-7,6}\\
\text{但是如果正着减的话,就会变成这样:}\\
a={1,8,-6,12,-12,20,-19,26}\\
注意,像有损前缀和一样的, for 语句要倒着写! 为啥呢?请看下面这个: a = 1 , 9 , 2 , 6 , 1 , 8 , 1 , 7 我们手玩是这样的(也就是无损的) b = 1 , 8 , − 7 , 4 , − 5 , 7 , − 7 , 6 但是如果正着减的话,就会变成这样: a = 1 , 8 , − 6 , 1 2 , − 1 2 , 2 0 , − 1 9 , 2 6
无损
1 2 for (int i = 1 ;i <= n;i++) b[i] = a[i] - a[i-1 ];
有损
1 2 for (int i = n;i;i--) b[i] -= b[i - 1 ];
二分查找
求解问题
求函数零点。
实现
upper_bound和lower_bound
非常的简单
离散化
求解问题
当数很大的时候,我们往往不能直接拿来存数组,这就是离散化的思想了。
实现
三部曲
sort
unique
lower_bound 或者 upper_bound
例题:
其实道理是非常简单的,唯一的问题就是数据范围非常的大,导致无法直接计算。怎么办?
这时就可以运用离散化的思想了。
但是,当我们再细细看题的时候: Bessie在她的行走中最远到达距起始点1,000,000,000个单位长度。 我的天!!!这个天才Bessie走这么远,我们要是乖乖开数组的话空间会不够的!!!这时我们又可以从什么方向入手呢? 我们回到我们一开始进行模拟的步骤,如果我们进行以下的移动的话会很简单: 3 R 2 L 3 R
(第一行是位置,第二行是经过次数)但如果变成这样会很麻烦: 1000000 R 500000 L 1000000 R
More Actions。。。这个要是要写表格的话会死人的吧;但是这简单来写的话是不是这样呢?
。。。这个要是要写表格的话会死人的吧;但是这简单来写的话是不是这样呢?
0
1
2
3
4
5
6
7
8
9
1
1
3
3
3
3
1
1
0
0
出处
为了锻炼读者的代码阅读能力 (懒) ,这个代码不添加任何注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <cstdio> #include <algorithm> using namespace std ;const int maxn = 2000010 ;int a[maxn],b[maxn],c[maxn],n,sum,ans;int o;char p;void Paint (int ll,int rr) { int l = lower_bound(b,b+n+1 ,ll)-b; int r = lower_bound(b,b+n+1 ,rr)-b; if (l>r)swap(l,r); c[l]++;c[r]--; } int main (void ) { scanf ("%d" ,&n); for (int i = 1 ;i <= n;i++) { cin >>a[i]>>p; if (p == 'L' )a[i]=-a[i]; b[i] = b[i-1 ] + a[i]; } sort(b,b+n+1 ); int m = unique(b,b+n+1 ) - b; for (int i = 1 ;i <= n;i++) { Paint(sum,sum+a[i]); sum+=a[i]; } sum = c[0 ]; for (int i = 1 ;i<m;i++) { if (sum > 1 )ans+=b[i]-b[i-1 ]; sum+=c[i]; } cout <<ans<<endl ; }
总结
离散化,说白了,就是把大数化小数的过程。
树状数组
先看一道题:
Q u z 1 : 何 为 树 状 数 组 \mathrm{Quz1:何为树状数组} Q u z 1 : 何 为 树 状 数 组 ?
就是没有右儿子的线段树。
处理的问题:
单点修改,区间查询(比较特殊,最小值:(只能查找左端点在1或者右端点在n的),和:无限制)
区间修改,单点查询(基础 差分技巧)
保存差分数组,利用差分技巧加减区间,单点查询就是查前缀和。
区间修改,区间查询(高级 差分技巧)
维护两个数组,一个t r e e 1 tree1 t r e e 1 保存t r e e [ i ] × ( i − 1 ) tree[i]\times (i-1) t r e e [ i ] × ( i − 1 ) ,一个正常保存差分。
答案就是:( y × a s k ( t r e e , y ) − ( x − 1 ) × a s k ( t r e e , x − 1 ) ) − ( a s k ( t r e e 1 , y ) − a s k ( t r e e 1 , x − 1 ) ) (y \times ask(tree,y)-(x-1)\times ask(tree,x-1))-(ask(tree1,y)-ask(tree1,x-1)) ( y × a s k ( t r e e , y ) − ( x − 1 ) × a s k ( t r e e , x − 1 ) ) − ( a s k ( t r e e 1 , y ) − a s k ( t r e e 1 , x − 1 ) )
具体的过程建议自己手推一下,加强记忆哦!
实现:
lowbit
1 #define lowbit(n) (n&-n)
单点修改
1 2 3 4 5 6 7 8 9 10 11 12 void add (int pos,int val) { for (int i = pos;i <= n;i+=i&(-i)) tr[i]+=val; } int ask (int *tr,int m) { int ans=0 ; for (int i = m;i>0 ;i-=i&(-i)) ans+=tr[i]; return ans; }
区间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void add (int *tr,int pos,int val) { for (int i = pos;i <= n;i+=i&(-i)) tr[i]+=val; } void add_ran (int l,int r,int val) { add(t1,l,val); add(t1,r+1 ,-val); add(t2,l,val*(l-1 )); add(t2,r+1 ,-val*r); } int ask (int *tr,int m) { int ans=0 ; for (int i = m;i>0 ;i-=i&(-i)) ans+=tr[i]; return ans; } int sum (int r) { return r*ask(t1,r)-ask(t2,r); } int get_ran (int l,int r) { return sum(r)-sum(l-1 ); }
这时候我们已经可以完美的解决问题了,就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <iostream> #include <algorithm> #include <cstdio> using namespace std ;const long long maxn = 100010 ;long long n,m,t,l,r,val;long long a,b;long long t1[maxn],t2[maxn];void add (long long *tr,long long pos,long long val) { for (long long i = pos;i <= n;i+=i&(-i)) tr[i]+=val; } void add_ran (long long l,long long r,long long val) { add(t1,l,val); add(t1,r+1 ,-val); add(t2,l,val*(l-1 )); add(t2,r+1 ,-val*r); } long long ask (long long *tr,long long m) { long long ans=0 ; for (long long i = m;i>0 ;i-=i&(-i)) ans+=tr[i]; return ans; } long long sum (long long r) { return r*ask(t1,r)-ask(t2,r); } long long get_ran (long long l,long long r) { return sum(r)-sum(l-1 ); } int main (void ) { scanf ("%lld%lld" ,&n,&m); for (long long i=1 ;i<=n;i++) { scanf ("%lld" ,&a); b=a-b; add(t1,i,b); add(t2,i,b*(i-1 )); b=a; } for (long long i=1 ;i<=m;i++) { scanf ("%lld" ,&t); if (t == 1 ) { scanf ("%lld%lld%lld" ,&l,&r,&val); add_ran(l,r,val); } else { scanf ("%lld%lld" ,&l,&r); printf ("%lld\n" ,get_ran(l,r)); } } return 0 ; }