文章目录
线段树、树状数组
树状数组能够解决的问题,那么线段树一定能解决。
树状数组与线段树相比,树状数组的代码短、常数很小
树状数组(
O
(
l
o
g
n
)
O(logn)
O(logn))
①给某个位置上的数加上一个数(如果要变成某个数的话,那么就先减去这个数,然后再加上你要变的这个数)
②求某一个前缀和
树状数组是一个在线做法(就是支持修改的做法),树状数组只能解决这个问题,可以支持 单点修改(①)和区间查询(②),那么基于这两个基本问题,我们就可以配合差分的思想然后来运用到区间修改和单点查询。
线段树
会比树状数组理解起来简单一些
操作:
①单点修改(只用修改信息需要变化的位置)
②区间查询
pushup:用子节点信息更新当前节点信息
build:在一段区间上初始化线段树
modify:修改
query:查询
线段树就是一个函数,树状数组就是一个比较复杂的函数
一、动态求连续区间和(线段树、树状数组)
树状数组写法
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N],tr[N];
int n,m;
int lowbit(int x)
{
return x&-x;
}
void add(int x,int v)//给第x个数加v,基本操作①
{
for(int i=x;i<=n;i+=lowbit(i))
tr[i]+=v;
}
int query(int x)//基础操作②
{
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
add(i,a[i]);
while(m--)
{
int k,x,y;
scanf("%d%d%d",&k,&x,&y);
if(k==0)
printf("%d\n",query(y)-query(x-1));
else add(x,y);
}
return 0;
}
线段树写法
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m;
int w[N];
struct node{
int l,r;
int sum;
}tr[N*4];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void build(int u,int l,int r)
{
if(l==r)
tr[u]={l,r,w[r]};
else{
tr[u]={l,r};//这个需要初始化
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
int query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r)
return tr[u].sum;
int mid=tr[u].l+tr[u].r>>1;
int sum=0;
if(l<=mid)sum=query(u<<1,l,r);
if(r>mid)sum+=query(u<<1|1,l,r);
return sum;
}
void modify(int u,int x,int v)
{
if(tr[u].l==tr[u].r)
tr[u].sum+=v;
else{
int mid=tr[u].r+tr[u].l >>1;
if(x<=mid)modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
build(1,1,n);
int k,a,b;
while(m--)
{
scanf("%d%d%d",&k,&a,&b);
if(k==0)
printf("%d\n",query(1,a,b));
else modify(1,a,b);
}
return 0;
}
二、数星星(树状数组)
任意门
给定一个题目,最核心的就是抽象化这个具体的操作。
树状数组下标一定要从1开始,所以当题目所给的x不是从1开始的话,我们就要把他变一下。
这道题我们跟着他的输入开始枚举,因为每一次的输入的数据都是以y递增的顺序进行,所以,我们读到的每一次的y都是当前最高的,所以这样子就很好我们操作了。
#include <iostream>
#include <cstdio>
#include<algorithm>
#include<cstring>
#include<sstream>
using namespace std;
const int N=32010;
int n;
int tr[N],level[N];
int lowbit(int x)
{
return x&-x;
}
int add(int x)
{
for(int i=x;i<N;i+=lowbit(i))
tr[i]++;
}
int sum(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
level[sum(x)]++;
add(x);
}
for(int i=0;i<n;i++)
printf("%d\n",level[i]);
return 0;
}
三、数列区间最大值(线段树)
任意门
这道题其实可以用ST表来写的,但是,ST表不支持修改,但是线段树吧就支持修改。
有的时候在做dp问题的时候,我们就可以用线段树来进行加速,这样子的话,可以把复杂度从 O ( n ) O(n) O(n)降低到 O ( l o g n ) O(logn) O(logn)
线段树的确慢一些,使用需谨慎
#include <iostream>
#include <cstdio>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<climits>
using namespace std;
const int N=1e5+10;
int n,m;
int w[N];
struct Node{
int l,r;
int maxv;
}tr[N*4];
void build(int u,int l,int r)
{
if(l==r)
tr[u]={l,r,w[r]};
else {
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
tr[u].maxv=max(tr[u<<1].maxv,tr[u<<1|1].maxv);
}
}
int query(int u,int l,int r)
{
if(tr[u].l>=l&&tr[u].r<=r)return tr[u].maxv;
int mid=tr[u].l+tr[u].r>>1;
int maxv=INT_MIN;
if(l<=mid)maxv=query(u<<1,l,r);
if(r>mid)maxv=max(maxv,query(u<<1|1,l,r));
return maxv;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
build(1,1,n);
int l,r;
while(m--)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(1,l,r));
}
return 0;
}
四、小朋友排队(树状数组)
任意门
只能交换相邻的小朋友,其实也就是冒泡排序的一个变形。
这里有几个结论,假设逆序对的个数为k
①交换次数至少是k
②在冒泡排序中,每次交换必然是逆序对数量-1
所以可以得出,逆序对交换次数为k
#include <iostream>
#include <cstdio>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<climits>
using namespace std;
typedef long long LL;
const int N=1e6+10;
int n,m;
int h[N],tr[N];
int sum[N];
int lowbit(int x)
{
return x&-x;
}
void add(int x,int v)
{
for(int i=x;i<N;i+=lowbit(i))
tr[i]+=v;
}
int query(int x)
{
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&h[i]),h[i]++;
//求每个数前面有多少个数比它大
for(int i=0;i<n;i++)
{
sum[i]=query(N-1)-query(h[i]);//算得是在他的前面插了多少个比他大的数
add(h[i],1);//树状数组存的是这个数有多少个
}
//每个数后面有多少个数比他小
memset(tr,0,sizeof tr);
for(int i=n-1;i>=0;i--)
{
sum[i]+=query(h[i]-1);
add(h[i],1);
}
LL res=0;
for(int i=0;i<n;i++)res+=(LL)sum[i]*(sum[i]+1)/2;//这个是高斯求和公式
cout<<res<<endl;
return 0;
}
五、油漆面积(线段树、扫描线)
任意门
这道题目是亚特兰蒂斯那道题目的简化版本,简化了一个离散化
像这类题目我们用扫描线法来做:
从每个垂直于矩形的竖边做一个垂直于x轴的线
这种方法比较容易处理的是覆盖问题。
扫描线:①数据量很大–>nlogn
②矩形斜着、三角形、圆形
这个时候我们就要计算几何来做了—>这个时候就是公式比较困难了
这道题非常特殊的是
struct {
int l,r;//左右边界
int cnt;//当前区间被覆盖的总次数
int len;//至少被覆盖一次的区间长度
}
这里的cnt和len都是不考虑父节点的情况下进行,永远只对上不对下。我们这里的线段树维护的是纵坐标。
#include <iostream>
#include <cstdio>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<climits>
using namespace std;
const int N=10010;
int n;
struct Segment
{
int x,y1,y2;
int k;
bool operator<(const Segment &t)const
{
return x<t.x;
}
}seg[N*2];
struct Node
{
int l,r;
int cnt,len;
}tr[N*4];
void pushup(int u)
{
if(tr[u].cnt>0)tr[u].len=tr[u].r-tr[u].l+1;
else if(tr[u].l==tr[u].r)tr[u].len=0;
else tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
}
void build(int u,int l,int r)
{
tr[u]={l,r};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void modify(int u,int l,int r,int k)
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].cnt+=k;
pushup(u);
}
else{
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid)modify(u<<1,l,r,k);
if(r>mid)modify(u<<1|1,l,r,k);
pushup(u);
}
}
int main()
{
scanf("%d",&n);
int m=0;
for(int i=0;i<n;i++)
{
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
seg[m++]={x1,y1,y2,1};
seg[m++]={x2,y1,y2,-1};
}
sort(seg,seg+m);
build(1,0,10000);
int res=0;
for(int i=0;i<m;i++)
{
if(i>0)res+=tr[1].len*(seg[i].x-seg[i-1].x);
modify(1,seg[i].y1,seg[i].y2-1,seg[i].k);
}
printf("%d\n",res);
return 0;
}
六、三体攻击(二分+前缀和)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 2000010;
int A, B, C, m;
LL s[N], b[N], bp[N];
int d[8][4] = {
{0, 0, 0, 1},
{0, 0, 1, -1},
{0, 1, 0, -1},
{0, 1, 1, 1},
{1, 0, 0, -1},
{1, 0, 1, 1},
{1, 1, 0, 1},
{1, 1, 1, -1},
};
int op[N / 2][7];
int get(int i, int j, int k)
{
return (i * B + j) * C + k;
}
bool check(int mid)
{
memcpy(b, bp, sizeof b);
for (int i = 1; i <= mid; i ++ )
{
int x1 = op[i][0], x2 = op[i][1], y1 = op[i][2], y2 = op[i][3], z1 = op[i][4], z2 = op[i][5], h = op[i][6];
b[get(x1, y1, z1)] -= h;
b[get(x1, y1, z2 + 1)] += h;
b[get(x1, y2 + 1, z1)] += h;
b[get(x1, y2 + 1, z2 + 1)] -= h;
b[get(x2 + 1, y1, z1)] += h;
b[get(x2 + 1, y1, z2 + 1)] -= h;
b[get(x2 + 1, y2 + 1, z1)] -= h;
b[get(x2 + 1, y2 + 1, z2 + 1)] += h;
}
memset(s, 0, sizeof s);
for (int i = 1; i <= A; i ++ )
for (int j = 1; j <= B; j ++ )
for (int k = 1; k <= C; k ++ )
{
s[get(i, j, k)] = b[get(i, j, k)];
for (int u = 1; u < 8; u ++ )
{
int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
s[get(i, j, k)] -= s[get(x, y, z)] * t;
}
if (s[get(i, j, k)] < 0) return true;
}
return false;
}
int main()
{
scanf("%d%d%d%d", &A, &B, &C, &m);
for (int i = 1; i <= A; i ++ )
for (int j = 1; j <= B; j ++ )
for (int k = 1; k <= C; k ++ )
scanf("%lld", &s[get(i, j, k)]);
for (int i = 1; i <= A; i ++ )
for (int j = 1; j <= B; j ++ )
for (int k = 1; k <= C; k ++ )
for (int u = 0; u < 8; u ++ )
{
int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
bp[get(i, j, k)] += s[get(x, y, z)] * t;
}
for (int i = 1; i <= m; i ++ )
for (int j = 0; j < 7; j ++ )
scanf("%d", &op[i][j]);
int l = 1, r = m;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", r);
return 0;
}
七、螺旋折线(数学找规律)
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
int main()
{
int x, y;
cin >> x >> y;
if (abs(x) <= y) // 在上方
{
int n = y;
cout << (LL)(2 * n - 1) * (2 * n) + x - (-n) << endl;
}
else if (abs(y) <= x) // 在右方
{
int n = x;
cout << (LL)(2 * n) * (2 * n) + n - y << endl;
}
else if (abs(x) <= abs(y) + 1 && y < 0) // 在下方
{
int n = abs(y);
cout << (LL)(2 * n) * (2 * n + 1) + n - x << endl;
}
else // 在左方
{
int n = abs(x);
cout << (LL)(2 * n - 1) * (2 * n - 1) + y - (-n + 1) << endl;
}
return 0;
}
八、差分(一维差分)
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int s[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> s[i];
for (int i = n; i; i -- ) s[i] -= s[i - 1];
while (m -- )
{
int l, r, c;
cin >> l >> r >> c;
s[l] += c, s[r + 1] -= c;
}
for (int i = 1; i <= n; i ++ ) s[i] += s[i - 1];
for (int i = 1; i <= n; i ++ ) cout << s[i] << ' ';
cout << endl;
return 0;
}
九、差分矩阵(二维差分)
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1010;
const int inf = 0x3f3f3f;
int n, m, q;
int a[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
int x;
scanf("%d", &x);
a[i][j] += x;
a[i + 1][j] -= x;
a[i][j + 1] -= x;
a[i + 1][j + 1] += x;
}
while (q -- )
{
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
a[x1][y1] += c;
a[x2 + 1][y1] -= c;
a[x1][y2 + 1] -= c;
a[x2 + 1][y2 + 1] += c;
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= m; j ++ ) printf("%d ", a[i][j]);
puts("");
}
return 0;
}