前缀和
对于静态的区间和多次查询操作,可以用前缀和来快速完成
假设已有数组 ,那么前缀和可以表示为
若已知 ,则有
若想查询区间 的数据之和
则有
int a[200010];
int sum[200010];
void solve()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++)
{
cin >> a[i];
sum[i] = sum[i-1] + a[i];
cout << sum[i] << " ";
}
}
差分
差分数组记录的是当前元素与前一个元素的差,特定的,有
假设已有数组,那么差分数组可以表示为
性质:前缀和的差分 = 差分的前缀和 = 原数组
用处:
- 对于多次区间整体加或减少k的操作,若暴力修改复杂度会很高
- 此时可以对原数组求差分数组,然后再差分数组上可以快速修改
过程:
- 求得
数组的差分数组
- 对于每次区间
整体加
的操作,可以让
同时
- 最后对差分数组
求前缀和数组,得到的数组即为修改后的新数组
由于前缀和是线性传递,当 后会使得前缀和数组
全部加
当 后会使得前缀和数组
全部减
区间 加
减
抵消,最终只保留区间
整体加
int a[200010];
int b[200010];
int sum[200010];
void solve()
{
int n,m;
cin >> n >> m;
a[0] = 0;
for(int i = 1;i <= n;i++)
{
cin >> a[i];
//求得a的差分数组b
b[i] = a[i] - a[i-1];
}
//每次将区间[l,r]整体加k
for(int i = 1;i <= m;i++)
{
int l,r,k;
cin >> l >> r >> k;
b[l] += k;
b[r+1] -= k;
}
//求得前缀和数组结果即为最终操作后的数组
for(int i = 1;i <= n;i++)
{
sum[i] = sum[i-1] + b[i];
cout << sum[i] << " ";
}
}
离散化
简单来说,就是用某个元素在数组中的排名来代表该元素进行操作
若有n组数据(n < 1e6),每组数据a[i]有一个唯一的编号id[i] < 1e9
若每次操作为修改某个编号id[i]的数据a[i],此时暴力查找编号时间复杂度太高,用下标表示又会因为空间复杂度太高爆掉
此时就用上了离散化技巧:
先对编号id进行排序,然后每次操作可以直接二分查找id在数组中的位置p
找到位置p后修改a[p]即可,此时用到的空间大小仅为数据的数量 n<1e6
int a[200010];
int id[200010];
int rk[200010];
void solve()
{
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++)
{
cin >> a[i] >> id[i];
//用一个rank数组记录id的排名
rk[i] = id[i];
}
//对rank数组排序
sort(rk+1,rk+1+n);
for(int i = 1;i <= m;i++)
{
int x;
cin >> x;
//二分查找 id = x 在rk数组中的位置
int p = lower_bound(rk+1,rk+1+n,x) - rk;
a[p] += x;
}
}
对于有些情况下,会有相同的数据,需要判断相同数据是否会造成影响然后考虑是否去重
去重: std::unique(begin,end)
- 前提需要数组已经排好序,而且该操作不是删掉数据,而是把多余的数组放在数组最后
- 该函数返回值为最后一个不重复的元素的下一个元素的首地址,去重后的数组长度为该地址减去数组首地址即为去重后数组长度
int rk[200010];
void solve()
{
int n;
cin >> n;
for(int i = 1;i <= n;i++)
{
cin >> rk[i];
}
sort(rk+1,rk+1+n);
//这里的减一是因为数组从0开始计数
//eg:长度为6的数组:a[0] a[1]...a[5]
//减一代表去重后数组最后一个重复元素的下标
int len = unique(rk+1,rk+1+n) - rk - 1;
for(int i = 1;i <= len;i++)
{
cout << rk[i];
}
}