前缀和练习
题解
(没补的几个题感觉对数学的要求好高,暂时不想懒得学了
前缀和、差分(某个区间加一次函数、二次函数)
(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](pos为选定的起点,n为数组大小) 加上
x
(
x
是
起
点
记
为
1
,
往
后
依
次
+
1
)
x(x是起点记为1,往后依次+1)
x(x是起点记为1,往后依次+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+2n2−2n 就等于
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}
2n2−2n=2(n−1)(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[i−1] 的时候,我们就操作
h
[
i
]
−
h
[
i
−
1
]
h[i]-h[i-1]
h[i]−h[i−1] 次,如果
h
[
i
]
<
=
h
[
i
−
1
]
h[i] <= h[i-1]
h[i]<=h[i−1] 我们就没必要操作,在之前操作的时候补上它。
#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|
∣u−v∣ 的能量,求串中的所有能量。
思路:
模拟一下就可以发现维护一个
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=ai−ai−1 (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=ai−ai−1 (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 中选两个数的方法可分为四类
- 选 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 一正一负的前提下,尽量多地采用这种操作,更快接近目标。
- 选 b 1 , b j , ( 2 < = j < = n ) b_1,b_j, \ (2<=j<=n) b1,bj, (2<=j<=n)
- 选 b i , b n + 1 , ( 2 < = i < = n ) b_i,b_{n+1}, \ (2<=i<=n) bi,bn+1, (2<=i<=n)
- 选 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|
∣p−q∣ 个未配对,每个可以选与
b
1
b_1
b1 或
b
n
+
1
b_{n+1}
bn+1 配对,即执行第
2
2
2 或第
3
3
3 类操作,共需
∣
p
−
q
∣
|p-q|
∣p−q∣ 次
综上所述,最少操作次数为
m
i
n
(
p
,
q
)
+
∣
p
−
q
∣
=
m
a
x
(
p
,
q
)
min(p,q)+|p-q|=max(p,q)
min(p,q)+∣p−q∣=max(p,q) 次
要消除剩下的
∣
p
−
q
∣
|p-q|
∣p−q∣ 对,我们可以任意选
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,...∣p−q∣ 次
b
1
b_1
b1 进行操作,也就会产生
∣
p
−
q
∣
+
1
|p-q|+1
∣p−q∣+1 种不同的
b
1
b_1
b1 的值,即最终得到的
a
a
a 可能有
∣
p
−
q
∣
+
1
|p-q|+1
∣p−q∣+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
bi−1 的数都减去
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
bi−1 的数都减去
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+1−1, dbi+1。其含义是:“身高减小
1
1
1 ” 的影响从
a
i
+
1
a_i+1
ai+1 开始,持续到
b
i
−
1
b_i-1
bi−1。最后
c
c
c 就是
d
d
d 的前缀和