本篇包含6道序列差分练习题及题解,难度由模板到提高
语文考试结束了,成绩还是一如既往地有问题。
语文老师总是写错成绩,所以当她修改成绩的时候,总是累得不行。她总是要一遍遍地给某些同学增加分数,又要注意最低分是多少。你能帮帮她吗?
第一行有两个整数 n n n, p p p,代表学生数与增加分数的次数。
第二行有 n n n 个数, a 1 ∼ a n a_1 \sim a_n a1∼an,代表各个学生的初始成绩。
接下来 p p p 行,每行有三个数, x x x, y y y, z z z,代表给第 x x x 个到第 y y y 个学生每人增加 z z z 分。
输出仅一行,代表更改分数后,全班的最低分。
3 2
1 1 1
1 2 1
2 3 1
2
对于 40 % 40\% 40% 的数据,有 n ≤ 1 0 3 n \le 10^3 n≤103。
对于 60 % 60\% 60% 的数据,有 n ≤ 1 0 4 n \le 10^4 n≤104。
对于 80 % 80\% 80% 的数据,有 n ≤ 1 0 5 n \le 10^5 n≤105。
对于 100 % 100\% 100% 的数据,有 n ≤ 5 × 1 0 6 n \le 5\times 10^6 n≤5×106, p ≤ n p \le n p≤n,学生初始成绩 $ \le 100 , , ,z \le 100$。
差分入门模板题
#include
using namespace std;
const int N = 5000005;
int n, p, ans;
int a[N],b[N];
int main()
{
cin >> n >> p;
for(int i = 1; i <= n; i ++) {
cin >> a[i];
b[i] = a[i] - a[i - 1];
}
while(p --)
{
int x, y, z;
cin >> x >> y >> z;
b[x] += z;
b[y + 1] -= z;
}
ans = 100;
for(int i = 1; i <= n; i ++) {
a[i] = a[i - 1] + b[i];
ans = min(ans, a[i]);
}
cout << ans;
return 0;
}
该铁路经过 N N N 个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第 i i i 段铁路连接了城市 i i i 和城市 i + 1 ( 1 ≤ i < N ) i+1(1\leq i
虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了 IC 卡。对于第 i i i 段铁路,需要花 C i C_i Ci 博艾元的工本费购买一张 IC 卡,然后乘坐这段铁路一次就只要扣 B i ( B i < A i ) B_i(B_i
Uim 现在需要出差,要去 M M M 个城市,从城市 P 1 P_1 P1 出发分别按照 P 1 , P 2 , P 3 , ⋯ , P M P_1,P_2,P_3,\cdots,P_M P1,P2,P3,⋯,PM 的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。
现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。
第一行两个整数, N , M N,M N,M。
接下来一行, M M M 个数字,表示 P i P_i Pi。
接下来 N − 1 N-1 N−1 行,表示第 i i i 段铁路的 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci。
一个整数,表示最少花费
9 10
3 1 4 1 5 9 2 6 5 3
200 100 50
300 299 100
500 200 500
345 234 123
100 50 100
600 100 1
450 400 80
2 1 10
6394
2 2 2 到 3 3 3 以及 8 8 8 到 9 9 9 买票,其余买卡。
对于 30 % 30\% 30% 数据 M = 2 M=2 M=2。
对于另外 30 % 30\% 30% 数据 N ≤ 1000 , M ≤ 1000 N\leq1000,M\leq1000 N≤1000,M≤1000。
对于 100 % 100\% 100% 的数据 M , N ≤ 1 0 5 , A i , B i , C i ≤ 1 0 5 M,N\leq 10^5,A_i,B_i,C_i\le10^5 M,N≤105,Ai,Bi,Ci≤105。
#include
using namespace std;
/*
在读入p数组的时候
给两个目的地之间做好标记
用差分来标记!
这样最后再求一遍前缀和
就会出来每条路线走过的次数!
对每条路线单独判定方案即可
这题卡常了。。
忘记了,以后还是别偷懒了
*/
typedef long long LL;
const int N = 100005, M = 100005;
LL n, m, sum;
int p[M];
LL a[N], b[N], c[N];
LL s1[N]; //差分数组
LL cnt[N]; //记录每个线路经过几次
LL ponder(int t) //t号线的最佳方案
{
return min(a[t] * cnt[t], c[t] + b[t] * cnt[t]);
}
int main()
{//第i段铁路连接 i 和 i + 1
cin >> n >> m;
cin >> p[1];
for(int i = 2; i <= m; i ++) {
cin >> p[i];
int a = min(p[i], p[i - 1]);
int b = max(p[i], p[i - 1]);
s1[a] += 1;
s1[b] -= 1; //这里不是b + 1哦....
}
for(int i = 1; i < n; i ++) {
cin >> a[i] >> b[i] >> c[i];
cnt[i] = cnt[i - 1] + s1[i]; //顺便求前缀和
}
for(int i = 1; i < n; i ++) {
sum += ponder(i); //斟酌一下对于这条路哪个方案更好
}
cout << sum;
return 0;
}
。。。。。。。。。。。。。。。。。。。。。。
上面算是入门级难度,接下来的几题是差分套差分的一些经典例子
小正方形亲眼看见了自己昔日的朋友被卷进了黑暗的深渊,然而它无力阻止……
现在它的朋友已经向它发起了攻击,因此小正方形不得不抵抗。
我们把山顶上的湖泊看作一条长度为 m m m 的直线,一开始水深都在水平线上,我们视作此时的水深为 ‘0’
接下来,在一瞬间,小正方形的"朋友"们跳起并扎入水中,导致在入水点的水降低而远离入水点的水升高,注意两个 “朋友” 可能在同一地点入水。
小正方形的每个朋友有一个体积数值 v v v,当体积为 v v v 的一个朋友跳入水中,我们设入水点为 i i i,将会导致 i − v + 1 i - v + 1 i−v+1 到 i i i 的水位依次降低 1 , 2 , ⋯ , v 1,2,\cdots,v 1,2,⋯,v
同样地,第 i i i 到 i + v − 1 i + v - 1 i+v−1 的水位会依次降低 v , v − 1 , ⋯ , 1 v,v - 1,\cdots,1 v,v−1,⋯,1.
相对应地, i − v i - v i−v 的水位不变, i − v − 1 i - v - 1 i−v−1 到 i − 2 ∗ v i - 2 * v i−2∗v 水位依次增加 1 , 2 , ⋯ , v 1,2,\cdots,v 1,2,⋯,v, i − 2 ∗ v i - 2 * v i−2∗v 到 i − 3 ∗ v + 1 i - 3 * v + 1 i−3∗v+1 水位依次增加 v , v − 1 , ⋯ , 1 v,v - 1,\cdots,1 v,v−1,⋯,1
同样, i + v i + v i+v 水位不变, i + v + 1 i + v + 1 i+v+1 到 i + 2 ∗ v i + 2 * v i+2∗v 水位增加 1 , 2 , ⋯ , v 1,2,\cdots,v 1,2,⋯,v, i + 2 ∗ v i + 2 * v i+2∗v 到 i + 3 ∗ v − 1 i + 3 * v - 1 i+3∗v−1 水位依次增加 v , v − 1 , ⋯ , 1 v,v - 1,\cdots,1 v,v−1,⋯,1
现在小正方形想要穿过这个湖,他想要知道在这 n n n 个"朋友"跳入水中后湖上每个节点的水位,你能帮帮它吗?
第一行为两个整数 n n n, m m m,表示"朋友"的数目与湖泊的宽度。
接下来 n n n 行,一行两个整数 v , x v,x v,x,表示第 i + 1 i + 1 i+1 个朋友的体积与入水点。
一行 m m m 个整数,第 i i i 个整数表示 i i i 号位的水深。
1 10
1 5
0 0 1 0 -1 0 1 0 0 0
2 10
2 6
3 1
-2 0 0 0 0 0 2 2 2 2
对于 30 % 30\% 30% 的数据, n < = 50 , m < = 500 n <= 50,m <= 500 n<=50,m<=500
对于 70 % 70\% 70% 的数据, n < = 1 0 5 , m < = 1 0 5 n <= 10^5,m <= 10^5 n<=105,m<=105
对于 100 % 100\% 100% 的数据, n < = 1 0 6 , m < = 1 0 6 , 1 < = v < = 10000 , 1 < = x < = m n <= 10^6,m <= 10^6,1 <= v <= 10000,1 <= x <= m n<=106,m<=106,1<=v<=10000,1<=x<=m
#include
using namespace std;
/*
差分套差分
求两遍前缀和
*/
const int N = 1000005, M = 2000005, dif = 50000;
//dif为偏移量,避免RE
int n, m;
int h[M];
int s[M]; //初级差分数组
int s2[M]; //最外层差分
int mm = 100000000, MM;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++) {
int v, x;
cin >> v >> x;
//记录下被影响到的边界
//前缀和要从边界开始求
mm = min(mm, x - 3 * v + 1 + dif);
MM = max(MM, x + 3 * v + dif);
s[x - 3 * v + 1 + dif] += 1;
s[x - 2 * v + 1 + dif] -= 2;
s[x + 1 + dif] += 2;
s[x + 2 * v + 1 + dif] -= 2;
s[x + 3 * v + 1 + dif] += 1;
}
for(int i = mm; i <= MM; i ++) {
s2[i] = s2[i - 1] + s[i];
}
for(int j = mm; j <= MM; j ++) {
h[j] = h[j - 1] + s2[j];
}
for(int i = 1; i <= m; i ++) cout << h[i + dif] << ' ';
return 0;
}
离开狭窄的洞穴,眼前豁然开朗。
天空飘着不寻常的雪花。
一反之前的幽闭,现在面对的,是繁华的街市,可以听见酒碗碰撞的声音。
这是由被人们厌恶的鬼族和其他妖怪们组成的小社会,一片其乐融融的景象。
诶,不远处突然出现了一些密密麻麻的小点,好像大颗粒扬尘一样。
离得近了点,终于看清楚了。
长着角的鬼们聚在一起,围观着另一只鬼的表演。
那”扬尘”,其实都是弹幕。
勇仪的招数之一,三步之内,所到之处弹幕云集,几乎没有生存可能。
为了强化这一技能,勇仪将对着一排柱子进行攻击。
旧地狱的柱子虽然无比坚固,但保险起见还是先要了解一下这样一套攻击对柱子有多少损伤,顺带也能检验练习的效果。
勇仪决定和其它鬼们商量商量…
“我知道妖怪之山的河童一族有一种叫做计算机的神奇道具,说不定可以借来用用”,萃香说道。
于是旧地狱的鬼族就决定请河城荷取来帮忙了。
“要记录【所有柱子的损伤程度】吗”,荷取问道。
经过进一步的询问,荷取发现他们仅仅需要【所有攻击都完成后】柱子的损伤程度。
任务了解地差不多了,荷取将其中的重要部分提取了出来,记录在了她的工作笔记本上:
(记录的内容见题目描述)
那么实验就这样开始了。
在惊天动地的碰撞声中,勇仪每完成一轮攻击,荷取都忠实地记录下对每根柱子产生的伤害。而此时勇仪就在旁边等待着记录完成,然后再进行下一轮的攻击。
地面上,天色渐晚。
“不想在这里留到深夜啊,不然就回不了家了”,荷取这样想着,手中依然在重复地向计算机中输入新产生的信息。
“真的必须一次一次地记录下每轮攻击对每个柱子产生的伤害吗?有没有更高效的方法?”这一念头在荷取的心中闪过…
(后续剧情在题解中,接下来请看T3)
N N N个柱子排成一排,一开始每个柱子损伤度为0。
接下来勇仪会进行 M M M次攻击,每次攻击可以用4个参数 l l l, r r r, s s s, e e e来描述:
表示这次攻击作用范围为第 l l l个到第 r r r个之间所有的柱子(包含 l l l, r r r),对第一个柱子的伤害为 s s s,对最后一个柱子的伤害为 e e e。
攻击产生的伤害值是一个等差数列。若 l = 1 l=1 l=1, r = 5 r=5 r=5, s = 2 s=2 s=2, e = 10 e=10 e=10,则对第1~5个柱子分别产生2,4,6,8,10的伤害。
鬼族们需要的是所有攻击完成之后每个柱子的损伤度。
第一行2个整数 N N N, M M M,用空格隔开,下同。
接下来 M M M行,每行4个整数 l l l, r r r, s s s, e e e,含义见题目描述。
数据保证对每个柱子产生的每次伤害值都是整数。
由于输出数据可能过大无法全部输出,为了确保你真的能维护所有柱子的损伤度,只要输出它们的异或和与最大值即可。
(异或和就是所有数字按位异或起来的值)
(异或运算符在c++里为^)
5 2
1 5 2 10
2 4 1 1
3 10
6 2
1 5 2 10
2 4 1 1
3 10
样例1:
第一次攻击产生的伤害:2 4 6 8 10
第二次攻击产生的伤害:0 1 1 1 0
所有攻击结束后每个柱子的损伤程度:2 5 7 9 10。
输出异或和与最大值,就是3 10。
样例2:
没有打到第六根柱子,答案不变
本题满分为100分,下面是4个子任务。(x/y)表示(得分/测试点数量)
妖精级(18/3): 1 ⩽ n 1\leqslant n 1⩽n, m ⩽ 1000 m\leqslant1000 m⩽1000。这种工作即使像妖精一样玩玩闹闹也能完成吧?
河童级(10/1): s = e s=e s=e,这可以代替我工作吗?
天狗级(20/4): 1 ⩽ n ⩽ 1 0 5 1\leqslant n\leqslant10^5 1⩽n⩽105, 1 ⩽ m ⩽ 1 0 5 1\leqslant m\leqslant10^5 1⩽m⩽105。小打小闹不再可行了呢。
鬼神级(52/2):没有特殊限制。要真正开始思考了。
以上四部分数据不相交。
对于全部的数据: 1 ⩽ n ⩽ 1 0 7 1\leqslant n\leqslant10^7 1⩽n⩽107, 1 ⩽ m ⩽ 3 × 1 0 5 1\leqslant m\leqslant3\times 10^5 1⩽m⩽3×105, 1 ⩽ l < r ⩽ n 1\leqslant l
所有输入输出数据以及柱子受损伤程度始终在 [ 0 , 9 × 1 0 18 ] [0,9\times 10^{18}] [0,9×1018]范围内。
由于种种原因,时间限制可能会比较紧,C++选手请不要使用cin读入数据。
by orangebird
#include
using namespace std;
/*
Lycanthropy那题的升级版
那题就是公差为1和-1的情况
这题需要自己算公差
差分套差分
求两遍前缀和
*/
typedef long long LL;
const int N = 10000005, M = 300005;
LL n, m, ans, MM;
LL hurt[N];
int s1[N], s2[N], h[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i ++) {
LL l, r, s, e;
cin >> l >> r >> s >> e;
LL d = (e - s) / (r - l); //求公差
s1[l + 1] += d;
s1[r + 1] -= d;
h[l] += s; //首项也要记录一下
h[r + 1] -= e; //不把这个删掉的话,后面不在这个范围内的也会累加上这个末项
}
for(int i = 1; i <= n; i ++) {
s2[i] = s2[i - 1] + s1[i];
hurt[i] = hurt[i - 1] + s2[i] + h[i];
ans ^= hurt[i];
if(MM < hurt[i]) MM = hurt[i];
}
cout << ans << ' ' << MM;
return 0;
}
给定一个长度为 n n n 的数列 a 1 , a 2 , ⋯ , a n {a_1,a_2,\cdots,a_n} a1,a2,⋯,an,每次可以选择一个区间 [ l , r ] [l,r] [l,r],使这个区间内的数都加 1 1 1 或者都减 1 1 1。
请问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列有多少种。
第一行一个正整数 n n n
接下来 n n n 行,每行一个整数,第 $i+1 $行的整数表示 a i a_i ai。
第一行输出最少操作次数
第二行输出最终能得到多少种结果
4
1
1
2
2
1
2
对于 100 % 100\% 100% 的数据, n ≤ 100000 , 0 ≤ a i ≤ 2 31 n\le 100000, 0 \le a_i \le 2^{31} n≤100000,0≤ai≤231。
#include
using namespace std;
/*
首先对原序列求差分
会得到一些正负数和0
目标就是把他们全都变成0
当然,第一项随便是几都行
所以先不要去改变第一项
把第一项放到一边去
这种区间操作+1、-1
只会改变差分序列的边界
序列的中间部分是不会改变的
所以最贪心的操作就是在序列中找到一个正数一个负数
让正数-1、负数+1
对应的操作就是让原序列的中间部分整体-1
所以就在差分序列中求出正数的总和和负数的总和的绝对值
min(cnt[+], cnt[-]) 就是最贪心的操作次数
做完以后还会剩下一些没法抵消的
那么就利用两个端点
整体+1或-1来抵消
端点的选择,可以是第一项,也可以是最后一项
改变第一项的话,就会多一种答案了
所以有多少个不能抵消的
就又会有多少个不同答案
*/
typedef long long LL;
const int N = 100005;
int n;
LL a[N], d[N];
LL pos, neg; //差分序列的正负数分别的绝对值总和
LL cnt, dif;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++) {
cin >> a[i];
d[i] = a[i] - a[i - 1];
if(i == 1) continue;
if(d[i] > 0) pos += d[i];
else neg += d[i];
}
cnt = min(pos, abs(neg));
dif = abs(pos - abs(neg)); //表示需要单独操作的次数
cnt += dif;
cout << cnt << endl << dif + 1;
return 0;
}
在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
面对海量租借教室的信息,我们自然希望编程解决这个问题。
我们需要处理接下来 n n n 天的借教室信息,其中第 i i i 天学校有 r i r_i ri 个教室可供租借。共有 m m m 份订单,每份订单用三个正整数描述,分别为 d j , s j , t j d_j,s_j,t_j dj,sj,tj,表示某租借者需要从第 s j s_j sj 天到第 t j t_j tj 天租借教室(包括第 s j s_j sj 天和第 t j t_j tj 天),每天需要租借 d j d_j dj 个教室。
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供 d j d_j dj 个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第 s j s_j sj 天到第 t j t_j tj 天中有至少一天剩余的教室数量不足 d j d_j dj 个。
现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
第一行包含两个正整数 n , m n,m n,m,表示天数和订单的数量。
第二行包含 n n n 个正整数,其中第 i i i 个数为 r i r_i ri,表示第 i i i 天可用于租借的教室数量。
接下来有 m m m 行,每行包含三个正整数 d j , s j , t j d_j,s_j,t_j dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从 1 1 1 开始的整数编号。
如果所有订单均可满足,则输出只有一行,包含一个整数 0 0 0。否则(订单无法完全满足)
输出两行,第一行输出一个负整数 − 1 -1 −1,第二行输出需要修改订单的申请人编号。
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
-1
2
【输入输出样例说明】
第 1 1 1份订单满足后, 4 4 4天剩余的教室数分别为 0 , 3 , 2 , 3 0,3,2,3 0,3,2,3。第 2 2 2 份订单要求第 2 2 2天到第 4 4 4 天每天提供 3 3 3个教室,而第 3 3 3 天剩余的教室数为 2 2 2,因此无法满足。分配停止,通知第 2 2 2 个申请人修改订单。
【数据范围】
对于10%的数据,有 1 ≤ n , m ≤ 10 1≤ n,m≤ 10 1≤n,m≤10;
对于30%的数据,有 1 ≤ n , m ≤ 1000 1≤ n,m≤1000 1≤n,m≤1000;
对于 70%的数据,有 1 ≤ n , m ≤ 1 0 5 1 ≤ n,m ≤ 10^5 1≤n,m≤105;
对于 100%的数据,有 1 ≤ n , m ≤ 1 0 6 , 0 ≤ r i , d j ≤ 1 0 9 , 1 ≤ s j ≤ t j ≤ n 1 ≤ n,m ≤ 10^6,0 ≤ r_i,d_j≤ 10^9,1 ≤ s_j≤ t_j≤ n 1≤n,m≤106,0≤ri,dj≤109,1≤sj≤tj≤n。
NOIP 2012 提高组 第二天 第二题
2022.2.20 新增一组 hack 数据
#include
#include
using namespace std;
/*
明显的区间修改和单点查询
m*n的复杂度明显不行
偷看标签发现了二分。。。
确实是单调的
越是编号靠后的人
越难以成功申请到教室
因此可以用二分来优化
*/
typedef long long LL;
const int N = 1000005;
struct Order
{
int d, s, e;
}ord[1000005]; //订单
int n, m;
LL r[N]; //每天可用于租借地教室数量
LL b[N]; //教室的差分数组,求前缀和可得教室数组a
LL a[N];
bool check(int x) //检验x号订单能否成功
{
memset(b, 0, sizeof b);
memset(a, 0, sizeof a);
for(int i = 1; i <= x; i ++) {
int d = ord[i].d, s = ord[i].s, e = ord[i].e;
b[s] += d;
b[e + 1] -= d;
}
for(int i = 1; i <= n; i ++) {
a[i] = a[i - 1] + b[i];
if(r[i] < a[i]) return false;
} //求一遍前缀和得到每天需要的教室数量
return true;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m; //天数和订单数量
for(int i = 1; i <= n; i ++) cin >> r[i];
for(int i = 1; i <= m; i ++) {
cin >> ord[i].d;
cin >> ord[i].s;
cin >> ord[i].e;
}
int l = 1, r = m;
while(l <= r)
{
int mid = (l + r) / 2;
if(check(mid)) l = mid + 1;
else r = mid - 1;
}
if(l == m + 1) cout << 0;
else cout << -1 << '\n' << l;
return 0;
}