A : 阿克曼函数
题目描述
![](https://img-blog.csdnimg.cn/img_convert/6c73c5240547386cdf0961f253837da6.png)
输入
输入m和n。
输出
函数值
样例输入
2 3
样例输出
9
思路:
按照题意递归
code
#include<bits/stdc++.h>
using namespace std;
int akm(int m,int n){
if(m==0)return n+1;
if(n==0)return akm(m-1,1);
return akm(m-1,akm(m,n-1));
}
int main(){
int m,n;
cin>>m>>n;
cout<<akm(m,n)<<endl;
}
B 甲流病人初筛
题目描述
目前正是甲流盛行时期,为了更好地进行分流治疗,医院在挂号时要求对病人的体温和咳嗽情况进行检查,对于体温超过37.5度(含等于37.5度)并且咳嗽的病人初步判定为甲流病人(初筛)。现需要统计某天前来挂号就诊的病人中有多少人被初筛为甲流病人。
输入
第一行是某天前来挂号就诊的病人数n。(n<200)
其后有n行,每行是病人的信息,包括三个信息:姓名(字符串,不含空格,最多8个字符)、体温(float)、是否咳嗽(整数,1表示咳嗽,0表示不咳嗽)。每行三个信息之间以一个空格分开。
输出
按输入顺序依次输出所有被筛选为甲流的病人的姓名,每个名字占一行。之后在输出一行,表示被筛选为甲流的病人数量。
样例输入 复制
5
Zhang 38.3 0
Li 37.5 1
Wang 37.1 1
Zhao 39.0 1
Liu 38.2 1
样例输出 复制
Li
Zhao
Liu
3
思路:
按题意对数据进行筛选
code
#include<bits/stdc++.h>
using namespace std;
struct xx{
string name;
double tem;
int flag;
};
int main(){
int n;
cin>>n;
vector<xx>a(n+5);
vector<string>ans;
for(int i=0;i<n;i++){
cin>>a[i].name>>a[i].tem>>a[i].flag;
if(a[i].tem>=37.5&&a[i].flag==1)ans.push_back(a[i].name);
}
for(auto i:ans){
cout<<i<<endl;
}
cout<<ans.size()<<endl;
}
C 同行列对角线的格
内存限制:128 MB时间限制:1.000 S
题目描述
输入三个自然数N,i,j(1≤i≤n,1≤j≤n),输出在一个N*N格的棋盘中(行列均从1开始编号),与格子(i,j)同行、同列、同一对角线的所有格子的位置。
如:n=4,i=2,j=3表示了棋盘中的第二行第三列的格子,
当n=4,i=2,j=3时,输出的结果是:
(2,1) (2,2) (2,3) (2,4) 同一行上格子的位置
(1,3) (2,3) (3,3) (4,3) 同一列上格子的位置
(1,2) (2,3) (3,4) 左上到右下对角线上的格子的位置
(4,1) (3,2) (2,3) (1,4) 左下到右上对角线上的格子的位置
输入
一行,三个自然数N,i,j,相邻两个数之间用单个空格隔开(1≤N≤10)。
输出
第一行:从左到右输出同一行格子位置;
第二行:从上到下输出同一列格子位置;
第三行:从左上到右下输出同一对角线格子位置;
第四行:从左下到右上输出同一对角线格子位置。
其中每个格子位置用如下格式输出:(x,y),x为行号,y为列号,采用英文标点,中间无空格。相邻两个格子位置之间用单个空格隔开。
样例输入 复制
4 2 3
样例输出 复制
(2,1) (2,2) (2,3) (2,4)
(1,3) (2,3) (3,3) (4,3)
(1,2) (2,3) (3,4)
(4,1) (3,2) (2,3) (1,4)
思路
同一行同一列的输出很简单,在对角线的输出容易出错,可以对根据i与j的大小进行讨论
code
//同一行同一列的输出很简单,在对角线的输出容易出错,可以对根据i与j的大小进行讨论
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,x,y;
cin>>n>>x>>y;
for(int i=1;i<=n;i++){
printf("(%d,%d) ",x,i);
}
cout<<endl;
for(int i=1;i<=n;i++){
printf("(%d,%d) ",i,y);
}
cout<<endl;
for(int i=max(1,y-x);i<=n;i++){
int ny=y-x+i;
if(ny<=0)continue;
if(ny>n)break;
printf("(%d,%d) ",i,ny);
}
cout<<endl;
for(int i=min(n,x+y-1);i>=1;i--){
int ny=x+y-i;
if(ny>n||ny<=0)continue;;
printf("(%d,%d) ",i,ny);
}
cout<<endl;
}
D: 基因相关性
内存限制:128 MB时间限制:1.000 S
题目描述
为了获知基因序列在功能和结构上的相似性,经常需要将几条不同序列的DNA进行比对,以判断该比对的DNA是否具有相关性。
现比对两条长度相同的DNA序列。定义两条DNA序列相同位置的碱基为一个碱基对,如果一个碱基对中的两个碱基相同的话,则称为相同碱基对。接着计算相同碱基对占总碱基对数量的比例,如果该比例大于等于给定阈值时则判定该两条DNA序列是相关的,否则不相关。
输入
有三行,第一行是用来判定出两条DNA序列是否相关的阈值,随后2行是两条DNA序列(长度不大于500)。
输出
若两条DNA序列相关,则输出“yes”,否则输出“no”。
样例输入 复制
0.85
ATCGCCGTAAGTAACGGTTTTAAATAGGCC
ATCGCCGGAAGTAACGGTCTTAAATAGGCC
样例输出 复制
yes
思路
找出两个碱基对相同的个数,跟期望值p*N比较
code
#include<bits/stdc++.h>
using namespace std;
int main(){
double p;
cin>>p;
string a,b;
cin>>a>>b;
double cnt=0;
for(int i=0;i<a.size();i++){
if(a[i]==b[i])cnt++;
}
if(p*a.size()<=cnt)cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
E: [蓝桥杯2022初赛] 重复的数
内存限制:128 MB时间限制:1.000 S
题目描述
给定一个数列A = (a1, a2, ... , an),给出若干询问。
每次询问某个区间[l, r]内恰好出现k次的数有多少个。
输入
输入第一行包含一个整数n表示数列长度。
第二行包含n个整数a1, a2, ... , an,表示数列中的数。
第三行包含一个整数m表示询问次数。
接下来m行描述询问,其中第i行包含三个整数li, ri, ki表示询问[li, ri]区间内有多少数出现了ki次。
对于20% 的评测用例,n;m ≤ 500, 1 ≤ a1; a2; ... ; an ≤ 1000;
对于40% 的评测用例,n;m ≤ 5000;
对于所有评测用例,1 ≤ n;m ≤ 100000, 1 ≤ a1; a2; ... ; an ≤ 100000, 1 ≤ li ≤ ri ≤ n, 1 ≤ ki ≤ n。
输出
输出m行,分别对应每个询问的答案。
样例输入 复制
3
1 2 2
5
1 1 1
1 1 2
1 2 1
1 2 2
1 3 2
样例输出 复制
1
0
2
0
1
思路
莫队模板题,可以先百度学习一下📖🙏
code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 200010;
// 莫队
int n, q, c[N], cnt[N], freq[N], ans[N];
int sq;
struct query
{
int l, r, k, id;
bool operator<(const query &o) const
{
if (l / sq != o.l / sq)
return l < o.l;
if (l / sq & 1)
return r < o.r;
return r > o.r;
}
} que[N];
void add(int x) {
cnt[freq[c[x]]]--;
freq[c[x]]++;
cnt[freq[c[x]]]++;
};
void del (int x) {
cnt[freq[c[x]]]--;
freq[c[x]]--;
cnt[freq[c[x]]]++;
};
int main()
{
ios_base :: sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
sq = sqrt(n);
for (int i = 1; i <= n; ++i) {
cin >> c[i];
}
cin >> q;
for (int i = 0; i < q; ++i) {
int l, r, k;
cin >> l >> r >> k;
que[i] = {l, r, k, i};
}
int B = 500;
sort(que, que + q);
int l = 1, r = 0;
for (int i = 0; i < q; ++i) {
while (r < que[i].r) r++, add(r);
while (l > que[i].l) l--, add(l);
while (r > que[i].r) del(r), r--;
while (l < que[i].l) del(l), l++;
ans[que[i].id] = cnt[que[i].k];
}
for (int i = 0; i < q; ++i) {
cout << ans[i] << '\n';
}
return 0;
}
F [蓝桥杯2022初赛] 重新排序
内存限制:128 MB时间限制:1.000 S
题目描述
给定一个数组A和一些查询Li, Ri,求数组中第Li至第Ri个元素之和。
小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。
小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?
输入
输入第一行包含一个整数n。
第二行包含n个整数A1, A2, ..., An,相邻两个整数之间用一个空格分隔。
第三行包含一个整数m表示查询的数目。
接下来m行,每行包含两个整数Li、Ri ,相邻两个整数之间用一个空格分隔。
对于30% 的评测用例,n,m ≤ 50 ;
对于50% 的评测用例,n,m ≤ 500 ;
对于70% 的评测用例,n,m ≤ 5000 ;
对于所有评测用例,1 ≤ n,m ≤ 10^5,1 ≤ Ai ≤ 10^6,1 ≤ Li ≤ Ri ≤ n 。
输出
输出一行包含一个整数表示答案。
样例输入 复制
5
1 2 3 4 5
2
1 3
2 5
样例输出 复制
4
提示
原来的和为6 + 14 = 20,重新排列为(1, 4, 5, 2, 3) 后和为10 + 14 = 24,增加了4。
思路
贪心,为了使查询的结果和最大,我们希望查询次数越多的数,值越大;原来的和可以用前缀和求得,重新排序后的和=所有个数的值*其出现的次数,即对a[i]*p[i]进行累加,其中a[i]是原来数值,单调递减排列,p[i]是频数,按照单调递减排序,这样可以求得最大的和,然后相减即可。
对于频数数组的计算,若使用朴素算法会超时,可以用差分数组
code
//贪心,差分,前缀和
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define scf(x) scanf("%lld",&x);
//#define all(x) x.begin(),x.end()
signed main(){
int n;
scf(n);
vector<int>a(n+5),sum(n+5,0);//前缀和
vector<int>flag(n+5,0);//差分数组
vector<int>xx(n+5);//频数数组
for(int i=1;i<=n;i++){
scf(a[i]);
sum[i]=sum[i-1]+a[i];
}
int cnt1=0;
int q;
scf(q);
while(q--){
int l,r;
scf(l);
scf(r);
cnt1+=sum[r]-sum[l-1];
flag[l]++;
flag[r+1]--;
}
for(int i=1;i<=n;i++){
xx[i]=xx[i-1]+flag[i];
}
sort(a.begin()+1,a.begin()+n+1,greater<int>());
sort(xx.begin()+1,xx.begin()+n+1,greater<int>());
int cnt2=0;
for(int i=1;i<=n;i++){
//cout<<flag[i]<<" "<<i<<endl;
if(xx[i]==0)break;
cnt2+=xx[i]*a[i];
}
// cout<<cnt1<<" "<<cnt2<<endl;
printf("%lld\n",cnt2-cnt1);
}
G士兵队列训练
内存限制:128 MB时间限制:1.000 S
题目描述
某部队进行新兵队列训练,将新兵从一开始按顺序依次编号,并排成一行横队,训练的规则如下:从头开始1至2报数,凡报到2的出列,剩下的向小序号方向靠拢,再从头开始进行1至3报数,凡报到3的出列,剩下的向小序号方向靠拢,继续从头开始进行1至2报数······以后从头开始轮流进行1至2报数、1至3报数直到剩下的人数不超过三人为止。
输入
本题有多个测试数据组,第一行为组数N,接着为N行新兵人数,新兵人数不超过10000。
输出
共有N行,分别对应输入的新兵人数,每行输出剩下的新兵最初的编号,编号之间有一个空格。
样例输入 复制
2
20
40
样例输出 复制
1 7 19
1 19 37
思路
建立链表list,赋初值,通过报数间隔消除一部分数,然后将剩下的士兵数依次输出
code
#include<iostream>
#include<list>
//思路:建立链表list,赋初值,通过报数间隔消除一部分数,然后将剩下的士兵数依次输出
using namespace std;
int main(){
int t,n;
cin>>t;//t表示组数
while(t--){
cin>>n;//n表示每组人数
int k=2;//k表示初始报数间隔
list<int>mylist;//定义链表
list<int>::iterator it;//定义迭代器
//赋值
for(int i=1;i<=n;i++){
mylist.push_back(i);
}
//消除报数间隔人数
while(mylist.size()>3){
int num=1;
for(it=mylist.begin();it!=mylist.end();){
if(num++%k==0){
it=mylist.erase(it);
}else{
it++;
}
}
k==2?k=3:k=2;
}
//输出剩余的士兵
for(it=mylist.begin();it!=mylist.end();it++){
if(it!=mylist.begin()){
cout<<" ";
}
cout<<*it;
}
cout<<endl;
}
return 0;
}
H度度熊学队列
内存限制:128 MB时间限制:1.000 S
题目描述
度度熊正在学习双端队列,他对其翻转和合并产生了很大的兴趣。 初始时有 N 个空的双端队列(编号为 1 到 N ),你要支持度度熊的 Q 次操作。
①1 u w val 在编号为 u 的队列里加入一个权值为 val 的元素。(w=0表示加在最前面,w=1 表示加在最后面)。
②2 u w 询问编号为 u 的队列里的某个元素并删除它。( w=0 表示询问并操作最前面的元素,w=1 表示最后面)
③3 u v w 把编号为 v 的队列“接在”编号为 u 的队列的最后面。w=0 表示顺序接(队列 v 的开头和队列 u 的结尾连在一起,队列v 的结尾作为新队列的结尾), w=1 表示逆序接(先将队列 v 翻转,再顺序接在队列 u 后面)。且该操作完成后,队列 v 被清空。
输入
有多组数据。
对于每一组数据,第一行读入两个数 N 和 Q。
接下来有 Q 行,每行 3~4 个数,意义如上。
N≤150000,Q≤400000
1≤u,v≤N,0≤w≤1,1≤val≤100000
所有数据里 Q 的和不超过500000
输出
对于每组数据的每一个操作②,输出一行表示答案。
注意,如果操作②的队列是空的,就输出-1−1且不执行删除操作。
样例输入 复制
2 10
1 1 1 23
1 1 0 233
2 1 1
1 2 1 2333
1 2 1 23333
3 1 2 1
2 2 0
2 1 1
2 1 0
2 1 1
样例输出 复制
23
-1
2333
233
23333
提示
由于读入过大,C/C++ 选手建议使用读入优化。
思路
双端队列的语法题,主要是熟悉双端队列的语法,用list和deque均可
code
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
list<int>l[150005];
int main(){
// ios::sync_with_stdio(0);
// cin.tie(0),cout.tie(0);
int n,q;
while(cin>>n>>q){
for(int i=1;i<=n;i++){
l[i].clear();
}
while(q--){
//cout<<"--------------"<<endl;
int op,u,v,w,val;
cin>>op;
if(op==1){
cin>>u>>w>>val;
if(w==0){
l[u].push_front(val);//前面加
}
else l[u].push_back(val);//后面加
}
else if(op==2){
//cout<<222<<endl;
cin>>u>>w;
if(l[u].empty()){
cout<<-1<<endl;
continue;
}
if(w==0){
cout<<l[u].front()<<endl;
l[u].pop_front();//最前面的出列
}
else{
cout<<l[u].back()<<endl;
l[u].pop_back();//最后面出列
}
}
else if(op==3){//
cin>>u>>v>>w;
if(w==0){//u+v,v清空
l[u].insert(l[u].end(),l[v].begin(),l[v].end());
l[v].clear();
}
else{//v反转后,u+v,v清空
l[u].insert(l[u].end(),l[v].rbegin(),l[v].rend());
l[v].clear();
}
}
}
}
}
I黑盒子
内存限制:128 MB时间限制:1.000 S
题目描述
Black Box 是一种原始的数据库。它可以储存一个整数数组,还有一个特别的变量 ii。最开始的时候 Black Box 是空的.而 i=0i=0。这个 Black Box 要处理一串命令。
命令只有两种:
ADD(x):把 xx 元素放进 Black Box;
GET:ii 加 11,然后输出 Black Box 中第 ii 小的数。
记住:第 ii 小的数,就是 Black Box 里的数的按从小到大的顺序排序后的第 ii 个元素。
我们来演示一下一个有11个命令的命令串。(如下表所示)
序号 | 操作 | ii | 数据库 | 输出 |
1 | ADD(3) | 00 | 33 | / |
2 | GET | 11 | 33 | 33 |
3 | ADD(1) | 11 | 1,31,3 | / |
4 | GET | 22 | 1,31,3 | 33 |
5 | ADD(-4) | 22 | -4,1,3−4,1,3 | / |
6 | ADD(2) | 22 | -4,1,2,3−4,1,2,3 | / |
7 | ADD(8) | 22 | -4,1,2,3,8−4,1,2,3,8 | / |
8 | ADD(-1000) | 22 | -1000,-4,1,2,3,8−1000,−4,1,2,3,8 | / |
9 | GET | 33 | -1000,-4,1,2,3,8−1000,−4,1,2,3,8 | 11 |
10 | GET | 44 | -1000,-4,1,2,3,8−1000,−4,1,2,3,8 | 22 |
11 | ADD(2) | 44 | -1000,-4,1,2,2,3,8−1000,−4,1,2,2,3,8 | / |
现在要求找出对于给定的命令串的最好的处理方法。ADD 命令共有 mm 个,GET 命令共有 nn 个。现在用两个整数数组来表示命令串:
a1,a2,⋯,am:一串将要被放进 Black Box 的元素。例如上面的例子中 a=[3,1,-4,2,8,-1000,2]a=[3,1,−4,2,8,−1000,2]。
u1,u2,⋯,un:表示第 u_iui 个元素被放进了 Black Box 里后就出现一个 GET 命令。例如上面的例子中 u=[1,2,6,6]u=[1,2,6,6] 。输入数据不用判错。
输入
第一行两个整数 m 和 n,表示元素的个数和 GET 命令的个数。
第二行共 m 个整数,从左至右第 i 个整数为 ai,用空格隔开。
第三行共 n 个整数,从左至右第 i 整数为 ui,用空格隔开。
输出
输出 Black Box 根据命令串所得出的输出串,每行一个数字。
样例输入 复制
7 4
3 1 -4 2 8 -1000 2
1 2 6 6
样例输出 复制
3
3
1
2
思路
我用的方法时间复杂度比较高,就是在每次的GET的指令是进行排序,比较快的方法有双顶堆维护第k大数
code
#include<bits/stdc++.h>
using namespace std;
int main(){
int m,n;
cin>>m>>n;
int a[m+5];
for(int i=0;i<m;i++)cin>>a[i];
vector<int>b(m+5);
for(int i=1;i<=n;i++){
int x;
cin>>x;
sort(a,a+x);
cout<<a[i-1]<<endl;
}
}
J 集合运算
内存限制:512 MB时间限制:3.000 S
题目描述
给定N个集合,第1个集合Si有Ci个元素(集合可以包含两个相同的元素)。
集合中的每个元素都用1~10000 的正数表示。
查询两个给定元素i和j是否同时属于至少一个集合。
换句话说,确定是否存在一个数字k(1≤k≤N),使得元素i和元素j都属于Sk。
输入
输入的第1行包含一个整数N (1≤N≤1000),表示集合的数量。
第2~N+1行,每行都以数字Ci(1≤Ci≤10000)开始,后面有Ci个数字,表示该集合中的元素。
第N+2行包含一个数字Q(1≤Q≤200000),表示查询数。
接下来的Q行,每行都包含一对数字i和j(1≤i,j≤10000, i可以等于j), 表示待查询的元素。
输出
对于每个查询,如果存在这样的数字k,则输出“Yes",否则输出“No"
样例输入 复制
3
3 1 2 3
3 1 2 5
1 10
4
1 3
1 5
3 5
1 10
样例输出 复制
Yes
Yes
No
No
思路
先对每个集合进行预处理,统计每个数的个数,对于每次查询,遍历所有集合,若找到了满足条件的集合,退出循环(不退出可能会超时)
code
#include<bits/stdc++.h>
using namespace std;
unordered_map<int,int>mp[1005];
#define endl '\n'
#define scf(x) scanf("%d",&x)
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int n;
scf(n);
for(int i=0;i<n;i++){
int m;
scf(m);
for(int j=0;j<m;j++){
int x;
scf(x);
mp[i][x]++;
}
}
int q;
scf(q);
while(q--){
int x,y;
scf(x);
scf(y);
int flag=0;
for(int i=0;i<n;i++){
if(mp[i][x]>0&&mp[i][y]>0){
flag=1;
break;//找到后要退出循环,否则会超时
}
}
if(flag)printf("Yes\n");
else printf("No\n");
}
}
K打怪兽version-4
内存限制:128 MB时间限制:1.000 S
题目描述
有1只怪兽的血量为H
你现在有一个技能
你可以选择一个怪兽,假设它当前生命值为x
对其造成伤害之后,该怪兽会分裂成2个生命值为⌊x/2⌋的怪兽
问你需要使用多少次技能可以杀死所有怪兽
输入
H
1 <= H <= 1e12
输出
需要使用多少次技能可以杀死所有怪兽
样例输入 复制
2
样例输出 复制
3
提示
样例输入2
1000000000000
输出
1099511627775
思路
用递归计算:cal(x)=2*cal(x/2)+1;
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int cal(int n){
if(n==0)return 0;
return 2*cal(n/2)+1;
}
signed main(){
int n;
cin>>n;
cout<<cal(n)<<endl;
}
L打怪兽version-5
内存限制:128 MB时间限制:1.000 S
题目描述
有一只怪兽的血量为H
你现在有N个技能
技能i可以对怪兽造成Ai点伤害,但是需要Bi点魔力值
问你最少需要多少点魔力值,可以杀死该怪兽(其血量小于等于0即为死亡)
每个技能可以重复使用
输入
H N
A1 B1
A2 B2
.....
AN BN
1 <= H <= 1e4
1 <= N <= 1e3
1 <= Ai Bi <= 1e4
输出
最少需要多少点魔力值,可以杀死该怪兽
样例输入 复制
9 3
8 3
4 2
2 1
样例输出 复制
4
提示
样例输入2
9999 10
540 7550
691 9680
700 9790
510 7150
415 5818
551 7712
587 8227
619 8671
588 8228
176 2461
输出
139815
思路
背包问题,dp[i]表示杀死血量为零的怪兽需要的魔力,先初始化为inf,显然dp[0]=0;
状态转移方程dp[i]=min(dp[i],dp[max(0,i-a[j])]+b[j]) (i-a[j]可能为负值)
code
//背包问题
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int dp[10005];
int a[1005],b[1005];
int main(){
int h,n;
cin>>h>>n;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=h;i++){
for(int j=1;j<=n;j++){
dp[i]=min(dp[i],dp[max(0,i-a[j])]+b[j]);
}
}
cout<<dp[h]<<endl;
}
M打怪兽version-6
内存限制:128 MB时间限制:2.000 S
题目描述
有N只怪兽,每只怪兽都有一个坐标Xi,和其血量Hi
你有一个技能
每次技能你可以选择一个坐标x
对区间[x-D,x+D]的所有怪兽造成伤害A点
问你最少需要使用多少次技能可以杀死所有怪兽(其血量小于等于0即为死亡)
输入
N D A
X1 H1
X2 H2
.....
XN HN
1 <= N <= 2e5
0 <= D , A <= 1e9
1 <= Ai , Hi <= 1e9
输出
最少需要使用多少次技能可以杀死所有怪兽
样例输入 复制
3 3 2
1 2
5 4
9 2
样例输出 复制
2
提示
样例2
9 4 1
1 5
2 4
3 3
4 2
5 1
6 2
7 3
8 4
9 5
输出
5
思路
贪心策略,我们依次消灭怪兽,由于之前的(即左边的)怪兽已经消灭,那么为了尽可能对右边的怪兽造成伤害,应该以该怪兽的位置作为起点,长度为2*d的区间内的怪兽进行攻击;每次选择血量大于0的怪兽进行攻击,小于等于0则跳过。这里又需要进行区间修改、单点查询,可以用数状数组实现;
第二个需要解决的问题,对于血量为h的怪兽,我们需要攻击的次数,等于h/a向上取整,也就是
(h+a-1)/a;
还有就是,怪兽的坐标的范围回到1e9,不能直接用坐标作为序号表示血量,需要先离散化
code
//树状数组
//区间修改,单点查询
//离散化
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 20000005
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb(x) push_back(x)
int n,d,a;
int t[N];
vector<pair<int,int>>v;
vector<int>xx;
int lowbit(int x){
return x&(-x);
}
int get(int x){
return lower_bound(xx.begin(),xx.end(),x)-xx.begin()+1;
}
int sum(int x){//前缀和
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=t[i];
}
return ans;
}
void add(int x,int k){//x节点加上k,同时更新父节点
for(int i=x;i<=2e6;i+=lowbit(i)){
t[i]+=k;
}
}
signed main(){
ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>d>>a;
for(int i=0;i<n;i++){
int x,h;
cin>>x>>h;
xx.pb(x);
xx.pb(x+2*d+1);
v.push_back({x,h});
}
sort(all(v));
sort(all(xx));
xx.erase(unique(all(xx)),xx.end());
int ans=0;
for(auto it:v){
int l=it.first;//起点
int h=it.second+sum(get(l));//血量
if(h<=0)continue;
int k=(h+a-1)/a;
ans+=k;
//cout<<k<<endl;
add(get(l),-k*a),add(get(l+2*d+1),k*a);//区间修改
}
cout<<ans<<endl;
}