前缀和、 差分

前缀和练习
题解
(没补的几个题感觉对数学的要求好高,暂时不想懒得学了

前缀和、差分(某个区间加一次函数、二次函数)
(Blog)

任意区间加常数

如果我们要每次给区间 [ l , r ] [l,r] [l,r] 加一个常数 c c c,次数少可以暴力,对于次数多的时候我们可以采用差分,如果原数组不全为 0 0 0,就新定义一个数组全为 0 0 0 的数组 d d d,每次操作 d [ l ] + = c , d [ r + 1 ] − = c d[l]+=c,d[r+1]-=c d[l]+=c,d[r+1]=c,最后求一个前缀和再加上原数组就可以得到最后的数组。

限制区间加一次函数,二次函数

1.如果想给区间 [ p o s , n ] ( p o s 为 选 定 的 起 点 , n 为 数 组 大 小 ) [pos,n](pos为选定的起点,n为数组大小) [pos,n]posn 加上 x ( x 是 起 点 记 为 1 , 往 后 依 次 + 1 ) x(x是起点记为1,往后依次+1) xx1+1
我们可以这样操作:
a [ p o s ] + + a[pos]++ a[pos]++,给起点 + 1 +1 +1,然后求两遍前缀和即可。
2.如果加 A x + B Ax+B Ax+B,我们可以先 a [ p o s ] + = A a[pos]+=A a[pos]+=A 同时并纪录下每次的起点 p o s pos pos,然后求一遍前缀和,再给每个起点加一次 B B B,再求一遍前缀和即可
3.如果加 x 2 x^2 x2,思路是根据前缀和 S n = n ( n + 1 ) 2 = n 2 2 + n 2 S_n= \frac{n(n+1)}{2}= \frac{n^2}{2}+\frac{n}{2} Sn=2n(n+1)=2n2+2n,我们考虑给 S n + n 2 2 − n 2 S_n + \frac{n^2}{2}-\frac{n}{2} Sn+2n22n 就等于 n 2 n^2 n2,而 n 2 2 − n 2 = ( n − 1 ) ( n + 0 ) 2 \frac{n^2}{2}-\frac{n}{2}=\frac{(n-1)(n+0)}{2} 2n22n=2(n1)(n+0),就等价于在给起点 n n n 位置 + 1 +1 +1 的同时给之后的 n + 1 n+1 n+1 位置也 + 1 +1 +1
看代码

void change()
{
	for(int i = 1; i <= 10; ++i) a[i] += a[i-1];
}
void work()
{
	cin >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> pos;
		a[pos]++;
		a[pos + 1]++;
	}
	change();
	change();
	for(int i = 1; i <= 10; ++i)	cout << a[i] << " \n"[i==10];
	change();
	for(int i = 1; i <= 10; ++i)	cout << a[i] << " \n"[i==10];
}

在这里插入图片描述
看例题:
小w的糖果
看懂上边的操作之后这个题就很简单了
开三个数组维护三个操作即可
(虽然题目说的不让用 cin,cout,但我用了也没T

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const ll mod = 1e9 + 7;
ll n, m, pos;
ll a[maxn], b[maxn], c[maxn];
void change(ll p[])
{
	for(int i = 1; i <= n; ++i) p[i] = (p[i-1] + p[i]) % mod;
}
void work()
{
	cin >> n >> m;
	for(int i = 1;i <= n + 1; ++i) a[i] = b[i] = c[i] = 0;
	for(int i = 1; i <= m; ++i)
	{
		int op;cin >> op >> pos;
		if(op == 1)
			a[pos]++, a[n+1]--;
		else if(op == 2)
			b[pos]++;
		else c[pos]++, c[pos + 1]++;
	}
	change(a);
	change(b);change(b);
	change(c);change(c);change(c);
	for(int i = 1; i <= n; ++i)	cout << (a[i] + b[i] + c[i]) % mod << " \n"[i==n];
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

分割线

牛牛的猜球游戏
前缀置换
感觉有点难理解,很奇妙

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
const ll mod = 1e9 + 7;
ll n, m;
ll f[maxn][10]; // f[i][j] 第i轮第j号球的下标
ll a[maxn]; 
void work()
{
	cin >> n >> m;
	for(int i = 0; i <= 9; ++i) f[0][i] = i;
	for(int i = 1; i <= n; ++i)
	{
		for(int j = 0; j <= 9; ++j) f[i][j] = f[i-1][j];
		int l, r;cin >> l >> r;
		swap(f[i][l], f[i][r]);
	}
	while(m--)
	{
		int l, r;cin >> l >> r;
		for(int i = 0; i <= 9; ++i) a[f[l-1][i]] = i;
		for(int i = 0; i <= 9; ++i)
			cout << a[f[r][i]] << " \n"[i==9];
	}
}

int main()
{
	ios::sync_with_stdio(0);
	//int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

[NOIP2013-积木大赛
[NOIP2018-道路铺设
贪心
对于 h [ i ] > h [ i − 1 ] h[i]>h[i-1] h[i]>h[i1] 的时候,我们就操作 h [ i ] − h [ i − 1 ] h[i]-h[i-1] h[i]h[i1] 次,如果 h [ i ] < = h [ i − 1 ] h[i] <= h[i-1] h[i]<=h[i1] 我们就没必要操作,在之前操作的时候补上它。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 9;
const ll mod = 1e9 + 7;
ll n, m, k, Q;
ll h[maxn];
void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> h[i];
	ll ans = 0;
	for(int i = 1; i <= n; ++i)
		if(h[i] > h[i-1]) ans += h[i] - h[i-1];
	cout << ans << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	//int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

牛牛的Link Power I
题意:
给定一个 01 01 01 串,串中下标为 1 1 1 的每个位置可以看成一个节点,对于点对 ( u , v ) ( s [ u ] = s [ v ] = ′ 1 ′ ) (u,v)(s[u]=s[v]='1') (u,v)(s[u]=s[v]=1) 会产生 ∣ u − v ∣ |u-v| uv 的能量,求串中的所有能量。
思路:
模拟一下就可以发现维护一个 s u m sum sum 计算答案就好了
code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 9;
const ll mod = 1e9 + 7;
ll n, m;
ll f[maxn];
string s;
void work()
{
	cin >> n >> s;s = "@" + s;
	ll sum = -1, ans = 0, cnt = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(s[i] == '1')
		{
			if(sum == -1) sum = i, ++cnt;
			else
			{
				(ans += 1ll * i * cnt - sum + mod) %= mod;
				++cnt;
				sum += i;
			}
		}
	}
	cout << ans;
}

int main()
{
	ios::sync_with_stdio(0);
	//int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

差分

对于给定数列 a a a,它的差分数列 b b b 定义为:
b 1 = a 1 , b i = a i − a i − 1   ( 2 < = i < = n ) b_1=a_1,b_i=a_i-a_{i-1} \ (2<=i<=n)\\ b1=a1,bi=aiai1 (2<=i<=n)
容易发现前缀和与差分是一对互逆运算
差分数列 b b b 的前缀和数列就是 a a a a a a 数列的差分数列就是 b b b

写代码时如果不定义 b b b,那么读完对 a a a 进行差分就好了,记得要倒着

for(int i = n; i >= 1; --i) a[i] -= a[i-1];

然后修改

for(int i = 1; i <= m; ++i){
	int l, r, k;cin >> l >> r >> k;
	a[l] += k, a[r+1] -= k;
}

最后求一个前缀和转化一下就结束了

for(int i = 1; i <= n; ++i) a[i] += a[i-1];

例题
100. 增减序列
思路:
求出 a a a 的差分数列 b b b,其中 b 1 = a 1 , b i = a i − a i − 1   ( 2 < = i < = n ) b_1=a_1,b_i=a_i-a_{i-1} \ (2<=i<=n) b1=a1,bi=aiai1 (2<=i<=n)。令 b n + 1 = 0 b_{n+1}=0 bn+1=0
题目对序列 a a a 的操作,其实相当于每次选出 b 1 , b 2 , . . . , b n + 1 b_1,b_2,...,b_{n+1} b1,b2,...,bn+1 中的任意两个数,一个加 1 1 1,一个减 1 1 1。目标是把 b 2 , b 3 , . . . , b n b_2,b_3,...,b_n b2,b3,...,bn 全部变成 0 0 0,最终得到的数列 a a a 就是由 n n n b 1 b_1 b1 构成的。
b 1 , b 2 , b 3 , . . . , b n b_1,b_2,b_3,...,b_n b1,b2,b3,...,bn 中选两个数的方法可分为四类

  1. b i , b j ,   ( 2 < = i , j < = n ) b_i,b_j, \ (2<=i,j<=n) bi,bj, (2<=i,j<=n),这种操作会改变 b 2 , b 3 , . . . b n b_2,b_3,...b_n b2,b3,...bn 中两个数的值。应该保证在 b i , b j b_i,b_j bi,bj 一正一负的前提下,尽量多地采用这种操作,更快接近目标。
  2. b 1 , b j ,   ( 2 < = j < = n ) b_1,b_j, \ (2<=j<=n) b1,bj, (2<=j<=n)
  3. b i , b n + 1 ,   ( 2 < = i < = n ) b_i,b_{n+1}, \ (2<=i<=n) bi,bn+1, (2<=i<=n)
  4. b 1 , b n + 1 b_1,b_{n+1} b1,bn+1,这种情况没有意义,因为它不会改变 b 2 , b 3 , . . . , b n b_2,b_3,...,b_n b2,b3,...,bn 的值,相当于浪费了一次操作,一定不是最优解。

b 2 , b 3 , . . . , b n b_2,b_3,...,b_n b2,b3,...,bn 中的正数和为 p p p,负数总和的绝对值为 q q q。首先以正负数配对的方式尽量执行第一类操作,执行 m i n ( p , q ) min(p,q) min(p,q) 次,剩余 ∣ p − q ∣ |p-q| pq 个未配对,每个可以选与 b 1 b_1 b1 b n + 1 b_{n+1} bn+1 配对,即执行第 2 2 2 或第 3 3 3 类操作,共需 ∣ p − q ∣ |p-q| pq
综上所述,最少操作次数为 m i n ( p , q ) + ∣ p − q ∣ = m a x ( p , q ) min(p,q)+|p-q|=max(p,q) min(p,q)+pq=max(p,q)
要消除剩下的 ∣ p − q ∣ |p-q| pq 对,我们可以任意选 b 1 , b n + 1 b_1,b_{n+1} b1,bn+1
考虑 b 1 b_1 b1 的值就是最后全部相同的数,可以选 0 , 1 , 2 , 3 , . . . ∣ p − q ∣ 0,1,2,3,...|p-q| 0,1,2,3,...pq b 1 b_1 b1 进行操作,也就会产生 ∣ p − q ∣ + 1 |p-q|+1 pq+1 种不同的 b 1 b_1 b1 的值,即最终得到的 a a a 可能有 ∣ p − q ∣ + 1 |p-q|+1 pq+1

101. 最高的牛
思路:
题目中的 m m m 对关系带给我们的信息实际上是牛之间身高的相对大小关系。具体来说,我们建立一个数组 c c c,数组中起初全为 0 0 0。若一条关系指明 a i , b i a_i,b_i ai,bi 相互看见(不妨设 a i < b i a_i<b_i ai<bi),则把数组 c c c 中下标为 a i + 1 a_i+1 ai+1 b i − 1 b_i-1 bi1 的数都减去 1 1 1,意思是在 a i , b i a_i,b_i ai,bi 中间的牛,身高至少要比他们小 1 1 1,因为第 p p p 头牛是最高的,所以最终 c [ p ] = 0 c[p]=0 c[p]=0,其他的牛和第 p p p 头牛的身高差距就体现在数组 c c c 中。换言之,最后第 i i i 头牛的身高就等于 h + c i h+c_i h+ci
如果我们朴素执行把数组 c c c 中下标为 a i + 1 a_i+1 ai+1 b i − 1 b_i-1 bi1 的数都减去 1 1 1 的操作,复杂度显然不允许我们,考虑新建一个数组 d d d,对于每个 a i , b i a_i,b_i ai,bi ,令 d a i + 1 − 1 ,   d b i + 1 d_{a_i+1} - 1, \ d_{b_i}+1 dai+11, dbi+1。其含义是:“身高减小 1 1 1 ” 的影响从 a i + 1 a_i+1 ai+1 开始,持续到 b i − 1 b_i-1 bi1。最后 c c c 就是 d d d 的前缀和

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值