声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
学习笔记目录链接
ACM-ICPC在线模板
树状数组可以解决大部分基于区间上的更新以及求和问题。
也只能解决这些问题
对于树状数组初始化,我们可以直接遍历所有的点,并对所有的点update,这样的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),也可以用下面的方法 O ( n ) O(n) O(n)解决:
树状数组求逆序对,时间复杂度为 O ( ( n + m l o g ) m ) O((n+mlog)m) O((n+mlog)m)
注意我们是在集合a的数值范围(y)上建立树状数组,来维护 t r e e tree tree的前缀和的,保存的是数值val在a数组里的出现次数。
本题的数据达到了1e9,所以我们应该离散化。
但是蓝书上说如果数据大到要用离散化,就得用sort,那么用树状数组还不如用归并排序效率更高。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 5000000;
int n,m;
struct node{
int vis,id;
bool operator<(const node &t)const{
return vis < t.vis;
}
}a[N];
int b[N];
int tree[N];
int lowbit(int x){
return x & (-x);
}
void update(int x,int k){
for(;x <= n;x += lowbit(x))
tree[x] += k;
}
ll ask(int x){
ll res = 0;
for(;x;x -= lowbit(x))
res += tree[x];
return res;
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i].vis);
a[i].id = i;
}
stable_sort(a + 1,a + 1 + n);
for(int i = 1;i <= n;++i)
b[a[i].id] = i;
ll ans = 0;
for(int i = n;i > 0;i -- ){
ans += ask(b[i] - 1);
update(b[i],1);
}
printf("%lld\n",ans);
return 0;
}
‘v’图腾求法
倒序扫描序列a,利用树状数组求出每个a[i]后面有几个数比它大,记录为right[i]
正序扫描序列a,利用树状数组求出每个a[i]前面有几个数比它大,记录为left[i]
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’v’图腾的个数就是. ∑ i = 1 N l e f t [ i ] × r i g h t [ i ] ∑^{N}_{i=1}left[i]×right[i] ∑i=1Nleft[i]×right[i]
’^’图腾求法
倒序扫描序列a,利用树状数组求出每个a[i]后面有几个数比它小,记录为right[i]
正序扫描序列a,利用树状数组求出每个a[i]前面有几个数比它小,记录为left[i]
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’^’图腾的个数就是. ∑ i = 1 N l e f t [ i ] × r i g h t [ i ] ∑^{N}_{i=1}left[i]×right[i] ∑i=1Nleft[i]×right[i]
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 500000;
int n,m;
int a[N];
int tree[N];
int lowbit(int x){
return x & (-x);
}
void update(int x,int val){
for(;x <= n;x += lowbit(x))
tree[x] += val;
}
ll ask(int x){//查询小于等于x的数的个数
ll res = 0;
for(;x;x -= lowbit(x))
res += tree[x];
return res;
}
int Greater[N],Lower[N];
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;++i)
scanf("%d",&a[i]);
for(int i = 1;i <= n;++i){//正序求 左边 比自己小的个数或者大的个数
int x = a[i];
Greater[i] = ask(n) - ask(x);
Lower[i] = ask(x - 1);
update(x,1);
}
memset(tree,0,sizeof tree);
ll res1 = 0;
ll res2 = 0;
for(int i = n;i > 0;i--){
int x = a[i];
res1 += Greater[i] * (ll)(ask(n) - ask(x));
res2 += Lower[i] * (ll)ask(x - 1);
update(x,1);
}
cout<<res1<<" "<<res2<<endl;
return 0;
}
#include
#include
#include
#include
#include
using namespace std;
const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
ll tree[N];
int lowbit(int x){
return x & (-x);
}
void update(int x,int val){
for(;x <= n;x += lowbit(x))
tree[x] += val;
}
ll ask(int x){
ll res = 0;
for(;x;x -= lowbit(x))
res += tree[x];
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)
scanf("%d",&a[i]),update(i,a[i] - a[i - 1]);
for(int i = 1;i <= m;++i){
char s[2];
int l,r,d;
scanf("%s%d",s,&l);
if(*s == 'C'){
scanf("%d%d",&r,&d);
update(l,d),update(r + 1,-d);
}
else printf("%lld\n",ask(l));
}
return 0;
}
#include
#include
#include
#include
#include
using namespace std;
const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
ll tree[2][N],sum[N];
int lowbit(int x){
return x & (-x);
}
ll ask(int k,int x){
ll res = 0;
for(;x;x -= lowbit(x))
res += tree[k][x];
return res;
}
void add(int k,int x,int val){
for(;x <= n;x += lowbit(x))
tree[k][x] += val;
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i){
scanf("%d",&a[i]);
sum[i] = sum[i - 1] + a[i];
}
while(m--){
char op[2];
int l,r,d;
scanf("%s%d%d",op,&l,&r);
if(op[0] == 'C'){
scanf("%d",&d);
add(0,l,d);
add(0,r + 1,-d);
add(1,l,l * d);
add(1,r + 1,-(r + 1) * d);
}
else {
ll ans = sum[r] + (r + 1) * ask(0,r) - ask(1,r);
ans -= sum[l - 1] + l * ask(0,l - 1) - ask(1,l - 1);
printf("%lld\n",ans);
}
}
return 0;
}
树状数组+二分
我们发现,如果说第K头牛的前面有 A k Ak Ak头牛比它矮,那么它的身高Hk就是数值1 ~ n中第A k + 1 _k+1 k+1小的没有在 H k + 1 , H k + 2 , … , H n H_k+1,H_k+2,…,H_n Hk+1,Hk+2,…,Hn中出现过的数
所以说,我们需要建立一个长度为n的1序列b,刚开始都是1,然后n到1倒序扫描每一个 A i A_i Ai,对于每个 A i A_i Ai执行查询和修改操作.
也就是说这道题目的题意就是让我们,动态维护一个01序列,支持查询第k个1所在的位置,以及修改序列中的一个数值
实时求出剩余的数中的第k小的数
#include
#include
#include
#include
#include
using namespace std;
const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
int ans[N];
int tree[N];
int lowbit(int x){
return x & -x;
}
void update(int x,int val){
for(;x <= n;x += lowbit(x))
tree[x] += val;
}
int ask(int x){
int res = 0;
for(;x;x -= lowbit(x))
res += tree[x];
return res;
}
int main(){
scanf("%d",&n);
for(int i = 2;i <= n;++i)
scanf("%d",&a[i]);
for(int i = 1;i <= n;++i)
tree[i] = lowbit(i);
//update(i,1);
for(int i = n;i;i -- ){
int k = a[i] + 1;//前面有k个,自己的答案就是k+1
int l = 1,r = n;
while(l < r){
int mid = (l + r) >> 1;
if(ask(mid) >= k)r = mid;
else l = mid + 1;
}
ans[i] = r;
update(r,-1);
}
for(int i = 1;i <= n;++i)
printf("%d\n",ans[i]);
return 0;
}