主要通过《牛牛与颁奖比赛》来阐述差分思想,并实现。然后引入《二分 》,《Soda Machine》来巩固这个知识点。
牛牛与比赛颁奖 √
题目描述
解题思路
- 暴力解法:
即定义数组arr[n]
表示每个队伍过题的人数,然后遍历m个整数区间[l,r],对区间arr[l…r]中所有数都进行+1操作,最后对这n个队伍进行排序找出金银铜队伍的数目即可。- 改进:
最耗时的就是对区间arr[l…r]所有数进行+1操作。可以看到在[l…r]这个区间里面,相邻两只队伍的过题数目并没有变化,其中l-1
队伍比l队伍多一道题目, r队伍比r+1队伍多一道题目,所以可以利用差分的思想,只保留关键队伍变化的节点,即变化量,diff[i]
表示第i支队伍比第i-1支队伍过题数目多多少。那么每次读取一个区间[l,r],就可以diff[l] = diff[l]+1; diff[r+1]=diff[r+1]-1;
。diff定义为map<int, int> diff; // 表示第i支队伍比第i-1支队伍过题数目差多少
主要思想如图所示,x轴代表所有队伍,过第一题队伍区间[0,3],过第二题的队伍[1,3], 过第三题的队伍[3,8]可以看到,0,1,3,8是变化的关键节点,关键节点表示在该节点左右两边过题数目发生变化,增加或者减少。
其中可以看到
过题目数1个的有 0 4 5 6 7 8
过题目数2个的有1 2
过题目数3个的有3
可以看到 区间变化时,整个区间的差值没有发生变化,只有区间两端端点与前一个队伍的差值发生了变化比如[0,1), [1,3), (3,8)
那么就可以利用端点差值的变化来表示整个区间的变化
map<int, int> diff; // map默认按key值升序排序
// [0,3] 0-3队伍答对一题
diff[0] = diff[0]+1; // 从0开始,[0,3]区间增加了1题
diff[4] = diff[4]-1; // [0,3]区间+1题,第4队伍比第3队伍少1题
// [1,3]
diff[1] = diff[1]+1; // 从1开始,[1,3]区间增加了1题
diff[4] = diff[4]-1; // [1,3]区间+1题,第4队伍比第3队伍少1题
// [3,8]
diff[3] = diff[3]+1; // 从3开始,[3,8]区间增加了1题
diff[8+1] = diff[8+1]-1; // 直到8,到第9队伍,第9队伍比第8队伍少1题
如何看出每个队伍答对多少题呢?
由上图可知,变化的前缀和 last ,即为当前队伍的答对题目的个数,且在变化量之间区间[l,r]中的队伍个数,即为 答对last个题的队伍个数。
for (auto &p: diff) // 按照升序遍历发生变化的节点,前缀和【前面所有变化的和】就是当前区间答对题目的个数
{
cnt[last] += p.first - lastid; // [lastid,p.first] 这个区间队伍答对题目的个数是一样的都是last, 统计 答对last题目个数的队伍有多少个
lastid = p.first; // lastid保存上一个变化的节点
last = p.second+last; // last 保存前缀和, 即前面所有变化的值,即diff[i]累加
}
取排名为 [n/10] [n/4] [n/2]的队伍通过题目总数为金银铜牌线
利用cnt[i]代表答对题目数量为 i 个的队伍有多少只,然后倒序遍历 cnt 。
for (int i= SIZE-2; i >= 0; i--) {
cnt[i] += cnt[i+1]; // cnt[i] 保存答对数目 >= i 的队伍个数
if (j == -1 && cnt[i] >= (n+9)/10) j=i; // 一旦队伍个数 大于等于 (n+9)/10 为金牌线
if (y == -1 && cnt[i] >= (n+3)/4) y=i; // 一旦队伍个数 大于等于 (n+3)/4 为银牌线
if (t == -1 && cnt[i] >= (n+1)/2) t=i;// 一旦队伍个数 大于等于 (n+1)/2 为铜牌线
}
实现代码
#include <bits/stdc++.h>
using namespace std;
const int SIZE = 1e5+10;
int cnt[SIZE]; // 过题数量有多少只队伍
int main()
{
memset(cnt, 0, sizeof(cnt));
map<int, int> diff;
//map<int, int> cnt;
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
diff[l] = diff[l] + 1;
diff[r+1] = diff[r+1] - 1;
}
int lastid = 0, last = 0;
//lastid记录上一个 关键队伍的编号, last记录上一个关键队伍的过题数量
// 从左到右依次遍历
for (auto &p: diff)
{
cnt[last] += p.first - lastid;
lastid = p.first;
last = p.second+last;
//printf(" %d \n", last);
}
int j=-1, y=-1, t=-1;
for (int i= SIZE-2; i >= 0; i--) {
cnt[i] += cnt[i+1];
if (j == -1 && cnt[i] >= (n+9)/10) j=i;
if (y == -1 && cnt[i] >= (n+3)/4) y=i;
if (t == -1 && cnt[i] >= (n+1)/2) t=i;
}
// 奖牌线不得少于1题
j = max(j, 1);
y = max(y, 1);
t = max(t, 1);
printf("%d %d %d\n", cnt[j], cnt[y]-cnt[j], cnt[t]-cnt[y]);
return 0;
}
裁判最多多少次记得目标分数 √
题目表述
解题思路
判断裁判最多有多少个回答是正确的,即在某个正整数为答案时,裁判回答正确的个数最多。
暴力解法:
遍历所有可能的正整数[1…100000],然后判断裁判的回答是否正确并统计回答正确的个数保留最大值。
5 . 在5位置 该回答正确
8 + 小于8的位置 该回答正确
5 . 在5位置 该回答正确
8 - 在大于8的位置 该回答正确
可以将上述转换为区间操作 [5,5], [1,8], [5,5] , [8, inf], 这样就可以转换为上面的差分来做。
diff[5] = diff[5]+1
diff[6]=diff[6]-1diff[1]=diff[1]+1
diff[9]=diff[9]-1diff[5] = diff[5]+1
diff[6]=diff[6]-1diff[8] = diff[8]+1
diff[100100]=diff[100100]-1
代码实现
#include<bits/stdc++.h>
using namespace std;
const int SIZE = 1e5+100;
map<int, int> diff;
int n;
int cnt[SIZE+1]; // 在i处时猜对的有多少个数
int main() {
scanf("%d", &n);
for (int i = 1; i<= n; i++) {
int a;
char b;
scanf(" %d", &a);
scanf(" %c", &b);
//printf("%d %c ", a, b);
if (b == '.') {
diff[a] = diff[a] + 1;
diff[a+1] = diff[a+1] - 1;
} else if (b == '+') {
// < a
diff[1]= diff[1] + 1;
diff[a] = diff[a] - 1;
} else if (b == '-') {
// > a 都加1
diff[a+1] = diff[a+1] + 1;
diff[INT_MAX] = diff[INT_MAX] - 1;
}
}
int sum = 0, lastid = 0, maxn = 0;
for (auto & p: diff)
{
//cnt[sum] = p.first-lastid;
sum += p.second;
//lastid = p.first;
maxn = max(maxn, sum);
}
// 最多有多少个回答是回答正确的。
printf("%d\n", maxn);
return 0;
}
Soda Machine 最多喂多少头牛 √
题目描述
示例如上,大概思路是喂牛可以看成水平线,每头牛进食是一个区间,如上图所示第一头牛a进食区间[3,5],第二头牛b进食区间[4,8],第三头牛c进食区间[1,2],第四头牛d进食区间[5,10],其中soda machine安装在整数点上,可以看到安装在5位置处,最多有三头牛可以喂到。
输入一个N表示有N头牛,接下来N行,每行两个数表示该牛的进食区间,求放在哪可以喂最多的牛?
解析
和上述题目一样用差分来做
代码实现
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1000000000;
int n;
map<int, int> diff;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
int l,r;
scanf("%d%d", &l, &r);
diff[l] = diff[l] + 1;
diff[r+1] = diff[r+1] - 1;
}
int sum = 0, maxn = 1;
for (auto & p : diff) {
sum += p.second;
maxn = max(maxn, sum);
}
printf("%d\n", maxn);
return 0;
}