前缀和、差分与离散化

前缀和

对于静态的区间和多次查询操作,可以用前缀和来快速完成

假设已有数组a ,那么前缀和可以表示为 sum[i] = \sum_{j=1}^{i}a[j]

若已知  sum[i]  ,则有  sum[i+1] = sum[i] +a[i+1]

若想查询区间 [l,r] 的数据之和

则有 \sum_{i=l}^{r}a[i] = \sum_{i=1}^{r}a[i]-\sum_{i=1}^{l-1}a[i] = sum[r]-sum[l-1]

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] << " ";
	}
}

差分

差分数组记录的是当前元素与前一个元素的差,特定的,有  b[1] = a[1]

假设已有数组a,那么差分数组可以表示为 b[i] = a[i]-a[i-1]

性质:前缀和的差分 = 差分的前缀和 = 原数组

用处:

  • 对于多次区间整体加或减少k的操作,若暴力修改复杂度会很高
  • 此时可以对原数组求差分数组,然后再差分数组上可以快速修改

过程:

  • 求得a数组的差分数组b
  • 对于每次区间 [l,r] 整体加 k 的操作,可以让 b[l] +k 同时 b[r+1]-k
  • 最后对差分数组 b 求前缀和数组,得到的数组即为修改后的新数组

由于前缀和是线性传递,当 b[l]+k 后会使得前缀和数组 sum[l,l+1...n] 全部加 k

当 b[r+1]-k 后会使得前缀和数组 sum[r+1,r+2...n] 全部减 k

区间 [r+1,n] 加 k 减 k 抵消,最终只保留区间 [l,r] 整体加 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];
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值