NUIST OJ 1352 回顾 【差分】
题目
题目描述
第三次选择那些大晴天的日子,第三次行走在孤单的海岸线,第三次静静地种更多的花给自己看~
我们假设把海岸线分为n块,每块的分别标记为1…n,每块都可以种花,每次种花可以选择某个[left,right]的闭区间,每块种上一朵花.经过m次种花操作后, 输入t次区间, 根据输入的区间,求该区间内花的总数.
注意这一次,我们要看更多次的花儿,所以在第一行要输入看花的次数t
输入描述:
多组输入
对每组输入,第一行有三个整数n m t,分别代表总块数和种花的次数以及我们希望查询区间的次数.
(1 <= n, m, t<= 100000)
接下来的m行, 每行两个整数 L,R 代表[L,R]区间内每块种上一朵花.(1 <= L <= R <= n)
接下来的t行, 每行输入两个整数 a,b 代表最后要查询的花的总数的区间.(1 <= a <= b <= n)
原问题:
原问题详见
NUIST OJ 1350-1352 面朝大海,春暖花开【初识线段树】
差分是什么
所谓差分,以大家高中学到的数列概念来理解。
假设
n | 1 | 2 | 3 | …… | n |
---|---|---|---|---|---|
Dn | 0 | 1 | 3 | …… | i |
An | 1 | 2 | 5 | …… | An-1 +i |
Sn | 1 | 3 | 8 | …… | Sn-1 + An |
类似于这样的差值数列。
那么在本题中,在[a,b]区间上种花,实际上带来的结果就是,a比a-1的值多1,即Dn(n=a)加了1。这样,从a区间开始往后的每一个区间,因为An(n=a)的值加了1,而他们和前一项的差值Dn(n>a)没有改变,所以实际上他们的An(n>a)都比原来大了1。而到B结束则可以将Dn(n=b+1)的值减1,这样从b+1开始,后面的值都不会受到此次操作的影响了。这样,仅仅通过对两个Dn的改变,实际上修改了整整一串的值。大大减少了时间消耗。
求An数组和Sn数组
只需要利用公式,An=An-1+Dn即可计算出An;Sn=Sn-1+An即可计算出Sn
实际上就是对数组进行了两次循环加和的操作
快速求区间和
有了Sn数组,求区间和的操作就快很多了,不需要将区间内的An一个个加起来;
At~Al的和可以用Sl-St-1求得
代码呈现
#include<cstdio>
#include<cstring>
using namespace std;
int dat[100010];
int main()
{
int n, m, t, l, f, a, b;
while (scanf_s("%d %d %d",&n,&m,&t)!=EOF) {
memset(dat, 0, (n + 1)*sizeof(int));
for (int i = 0; i<m; i++) {
scanf("%d %d",&l,&f);
dat[l] += 1;
dat[f + 1] -= 1;
}
for (int i = 1; i <= n; i++)
dat[i] += dat[i - 1];
for (int i = 1; i <= n; i++)
dat[i] += dat[i - 1];
for (int i = 0; i<t; i++) {
scanf("%d %d", &a, &b);
printf("%d\n", dat[b] - dat[a - 1]);
}
}
return 0;
}
随便说说
差分在处理区段求和上有非凡的表现。虽然曾经我们都学过数列,但是真正用起来的时候却完全想不到它。渍渍渍,遗憾呐。
另外,差分在这里有一个小缺陷。必须所有数据全部录入完毕之后,根据完整的Dn表格生成An表格,再根据An的表格生成Sn的表格。若是生成完毕之后其中有一处需要修改,那么需要重新生成Dn和Sn,这样就失去了它的独特优势。
后记
照例膜拜ThinkSpirit各位大佬
第一个想到这个方法的大佬解说如下
THINKSPIRIT ALGORITHM TEAM TASK #1 解题报告