北化2023.2.6寒假集训十七题解

A : 阿克曼函数

题目描述

输入

输入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 个。现在用两个整数数组来表示命令串:

  1. a1,a2,⋯,am:一串将要被放进 Black Box 的元素。例如上面的例子中 a=[3,1,-4,2,8,-1000,2]a=[3,1,−4,2,8,−1000,2]。

  1. 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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值