2023寒假集训-进阶训练赛(十七)题解

问题 A: 阿克曼函数

题意:

给出阿克曼(Ackmann)函数定义,m,n定义域是非负整数(m≤3,n≤10),求函数值

思路:模拟递归即可,注意边界条件

代码:

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
ll A(ll m,ll n){
    if(m==0)   return n+1;
    else if(m>0 && n==0)   return A(m-1,1);
    else if(m>0 && n>0)   return A(m-1,A(m,n-1));
}
 
int main()
{
    ll m,n;
    cin>>m>>n;
    cout<<A(m,n);
} 

问题B: 甲流病人初筛

题意:

对于体温超过37.5度(含等于37.5度)并且咳嗽的病人初步判定为甲流病人(初筛),统计某天前来挂号就诊的病人中有多少人被初筛为甲流病人。

思路:

模拟即可

代码:

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int main()
{
    int n;
    string s;
    double c;
    int flag;
    cin>>n;
    int cnt=0;
    while(n--){
        cin>>s>>c>>flag;
        if(c>=37.5 && flag==1){
            cout<<s<<endl;
            cnt++; 
        }
    }
    cout<<cnt; 
}

问题 C: 同行列对角线的格

题意:

输入三个自然数N,i,j(1≤i≤n,1≤j≤n),输出在一个N*N格的棋盘中(行列均从1开始编号),与格子(i,j)同行、同列、同一对角线的所有格子的位置。

第一行:从左到右输出同一行格子位置;

第二行:从上到下输出同一列格子位置;

第三行:从左上到右下输出同一对角线格子位置;

第四行:从左下到右上输出同一对角线格子位置。

思路:

模拟。直线位置直接遍历行和列输出。

输出对角线位置时遍历所有位置,找与已知点连线斜率为1和-1的点即可。

代码:

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int main()
{
    int n,x,y;
    cin>>n>>x>>y;
    for(int i=1;i<=n;i++){
        printf("(%d,%d) ",x,i);
    }
    cout<<"\n";
    for(int i=1;i<=n;i++){
        printf("(%d,%d) ",i,y);
    }
    cout<<"\n";
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if((i-x)==(j-y))
                printf("(%d,%d) ",i,j);
        }
    }
    cout<<"\n";
    for(int i=n;i>=1;i--){
        for(int j=n;j>=1;j--){
            if((i-x)==-(j-y)){
                printf("(%d,%d) ",i,j);
            }
        }
    }
}

问题 D: 基因相关性

题意:

比对两条长度相同的DNA序列。定义两条DNA序列相同位置的碱基为一个碱基对,如果一个碱基对中的两个碱基相同的话,则称为相同碱基对。接着计算相同碱基对占总碱基对数量的比例,如果该比例大于等于给定阈值时则判定该两条DNA序列是相关的,否则不相关。

思路:

模拟。计算相同的字符数所占比例是否大于给出的阈值。

代码:

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
int main()
{
    double f;
    string s1,s2;
    cin>>f>>s1>>s2;
    int cnt=0;
    for(int i=0;i<s1.size();i++){
        if(s1[i]==s2[i])  cnt++;
    }
    if((1.0*cnt)/(1.0*s1.size())>f)  cout<<"yes";
    else    cout<<"no";
}

问题 E: [蓝桥杯2022初赛] 重复的数

题意:

给定一个数列A = (a1, a2, ... , an),给出若干询问。每次询问某个区间[l, r]内恰好出现k次的数有多少个。

思路:

莫对模板题,具体内容参考以下链接。

算法学习笔记(24): 莫队 - 知乎 (zhihu.com)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100000+5; 
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()
{
    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};
    }    
    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]<<endl; 
}

问题 F: [蓝桥杯2022初赛] 重新排序

题意:

给定一个数组A和一些查询Li, Ri,求数组中第Li至第Ri个元素之和。重新排列一下数组,使得最终每个查询结果的和尽可能地大。求相比原数组,所有查询结果的总和最多可以增加多少。

思路:

贪心+前缀和+差分

将给定的所有区间差分一次,再用前缀和累加,可得到原序列中每个元素出现的次数。

记录给定所有区间中出现次数最多的下标的个数,与数组中最大的数相乘,使出现次数较多的下标次数与较大的元素相乘。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=100000+5;
int n,m;
int a[N],s[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)    cin>>a[i];
    cin>>m;
    while(m--){
        int l,r;
        cin>>l>>r;
        s[l]++,s[r+1]--; 
    } 
    for(int i=1;i<=n;i++){
        s[i]+=s[i-1];
    }
    
    ll res1=0,res2=0;
    for(int i=1;i<=n;i++)
        res1+=(ll)a[i]*s[i];
    sort(a+1,a+1+n);
    sort(s+1,s+1+n);
    for(int i=1;i<=n;i++)
        res2+=(ll)a[i]*s[i];
        
    cout<<res2-res1; 
}

问题 G: 2.4.4 士兵队列训练

题意:

从头开始1至2报数,凡报到2的出列,剩下的向小序号方向靠拢,再从头开始进行1至3报数,凡报到3的出列,剩下的向小序号方向靠拢,继续从头开始进行1至2报数······以后从头开始轮流进行1至2报数、1至3报数直到剩下的人数不超过三人为止。

思路:

采用队列,用flag标记报2还是报3,当报2(或3)时,在队列中从前向后找 队列的size/2 次,遇到2(或3)时出列,其他的放回队列中,依次循环可挑出士兵且保证顺序不变,然后改变flag的值,一直到剩下的人数不超过3为止。

代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        queue<int> q;
        for(int i=1;i<=n;i++)    q.push(i);
        int flag=1;
        while(q.size()>3){
            int sz=q.size();
            if(flag){
                for(int i=0;i<sz/2;i++){
                    int t=q.front();
                    q.push(t);
                    q.pop();
                    q.pop();
                }
                if(sz%2==1){
                    int t=q.front();
                    q.pop();
                    q.push(t);
                }
            }
            else{
                for(int i=0;i<sz/3;i++){
                    int t=q.front();  q.push(t);  q.pop();
                    t=q.front();  q.push(t);  q.pop();
                    q.pop(); 
                } 
                while(sz%3!=0){
                    int t=q.front();  q.push(t);  q.pop();
                    sz--;
                }
            }
            flag=1-flag;
        }
        while(!q.empty()){
            cout<<q.front()<<' ';
            q.pop();
        }
        cout<<endl;
    }
} 

问题 H: 2.4.5 度度熊学队列

题意:

初始时有 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 被清空。

思路:

根据题意模拟。全部操作都是deque自带的操作,按照题意来写即可。

代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,q;
    cin>>n>>q;
    deque<int> d[n+5];
    while(q--){
        int op;
        cin>>op;
        int u,w,v,val;
        if(op==1){
            cin>>u>>w>>val;
            if(w)   d[u].push_back(val); 
            else    d[u].push_front(val);
        }   
        else if(op==2){
            cin>>u>>w;
            if(d[u].empty())   cout<<"-1"<<endl;
            else{
                int t;
                if(w){
                    t=d[u].back(); 
                    d[u].pop_back();
                }
                else{
                    t=d[u].front();
                    d[u].pop_front(); 
                }   
                cout<<t<<endl; 
            } 
        }
        else if(op==3){
            cin>>u>>v>>w;
            int sz;
            sz=d[v].size();
            if(!w){
                while(sz--){
                    int t=d[v].front();
                    d[v].pop_front();
                    d[u].push_back(t);
                }
            }
            else{
                while(sz--){
                    int t=d[v].back();
                    d[v].pop_back();
                    d[u].push_back(t);
                }
            }
        }
    }
}

问题 I: 2.4.6 黑盒子

题意:

Black Box 可以储存一个整数数组,还有一个变量 i。最开始的时候 Black Box 是空的.而 i=0。这个 Black Box 要处理一串命令。

命令只有两种:

  • ADD(x):把 x 元素放进 Black Box;

  • GET:i加 1,然后输出 Black Box 中第 i小的数。

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. w1,w2,⋯,wm:表示第 ui个元素被放进了 Black Box 里后就出现一个 GET 命令。例如上面的例子中 u=[1,2,6,6] 。输入数据不用判错。

思路:

根据题意模拟。全部操作都是deque自带的操作,按照题意来写即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
 
priority_queue<ll> q1;
priority_queue<ll,vector<ll>,greater<ll> >q2;
 
int main()
{
    ios::sync_with_stdio(false); 
    cin.tie(0);
    cout.tie(0);
    int m,n;
    cin>>m>>n;
    vector<ll> a(m+5);
    for(int i=1;i<=m;i++)    cin>>a[i];
    int num=1;
    for(int i=0;i<n;i++){
        int k;
        cin>>k;
        while(num<=k){
            q1.push(a[num]);
            ll t=q1.top();
            q1.pop();
            q2.push(t);
            num++;
        }
        q1.push(q2.top());
        cout<<q2.top()<<endl;
        q2.pop();
    }
}

问题 J: 2.4.7 集合运算

题意:

给定N个集合,第1个集合Si有Ci个元素(集合可以包含两个相同的元素)。集合中的每个元素都用1~10000 的正数表示。查询两个给定元素i和j是否同时属于至少一个集合。

即确定是否存在一个集合Sk(1≤k≤N),使得元素i和元素j都属于Sk。

思路:

使用n个vector容器存储数字,每输入i组数,都用find()函数遍历n个容器中是否同时存在这两个数。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
 
int main()
{
    ios::sync_with_stdio(false); 
    cin.tie(0);
    cout.tie(0);
    int n;
    cin>>n;
    vector<int>a[n+5];
    for(int i=0;i<n;i++){
        int cnt;
        cin>>cnt;
        for(int j=0;j<cnt;j++){
            int t;
            cin>>t;
            a[i].push_back(t);
        }
    }
    int q;
    cin>>q; 
    while(q--){
        int t1,t2;
        cin>>t1>>t2;
        int flag=0;
        for(int i=0;i<n;i++){
            vector<int>::iterator it1=find(a[i].begin(),a[i].end(),t1);
            vector<int>::iterator it2=find(a[i].begin(),a[i].end(),t2);
            if(it1!=a[i].end() && it2!=a[i].end()){
                flag=1;
                break;
            }
        }
        if(flag)    cout<<"Yes\n";
        else    cout<<"No\n";
    }
}

问题 K: 打怪兽version-4

题意:

有1只怪兽的血量为H,现在有一个技能,可以选择一个怪兽,假设它当前生命值为x

对其造成伤害之后,该怪兽会分裂成2个生命值为⌊x/2⌋的怪兽

问你需要使用多少次技能可以杀死所有怪兽

思路:

开始有cnt=1个怪兽,每次都要对cnt个怪兽都进行一次攻击,即攻击次数sum要加上cnt,攻击之后,每只怪兽的血量为原来的1/2,而怪兽数量变为原来的二倍,即cnt=cnt*2,一直攻击,直到每只怪兽的血量均为0。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
 
int main()
{
    ll h;
    cin>>h;
    ll cnt=1;
    ll sum=0;
    while(h){
        sum+=cnt;
        h/=2;
        cnt*=2;
    }   
    cout<<sum;
}

问题 L: 打怪兽version-5

题意:

有一只怪兽的血量为H,现在有N个技能,

技能i可以对怪兽造成Ai点伤害,但是需要Bi点魔力值,

问你最少需要多少点魔力值,可以杀死该怪兽(其血量小于等于0即为死亡),每个技能可以重复使用。

思路:

最小背包问题。

把A i点伤害当做物品的体积,B i 点魔力值当做物品的价值,N个技能等价于n 个物品,并且每件物品可以重复使用。题目等价于求背包体积为[ H , ∞ ]的最小价值。

由于求最小值,初始化背包的价值为正无穷,体积为0的情况下最小值为0。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
 
int main()
{
    int h,n;
    cin>>h>>n;
    int f[10000+5];
    for(int i=0;i<10000+5;i++)  f[i]=1000000000+7;
    f[0]=0;
    for(int i=1;i<=n;i++){
        int a,b;
        cin>>a>>b;
        for(int j=0;j<=h;j++){
            f[j]=min(f[max(j-a,0)]+b,f[j]);
        }
    }
    cout<<f[h];
}

问题 M: 打怪兽version-6

题意:

有N只怪兽,每只怪兽都有一个坐标Xi,和其血量Hi。有一个技能,每次技能你可以选择一个坐标x,

对区间[x-D,x+D]的所有怪兽造成伤害A点。

问你最少需要使用多少次技能可以杀死所有怪兽(其血量小于等于0即为死亡)

思路:

简单的贪心策略是:首先对怪兽所再的位置进行排序,之后从左往右遍历,依次杀死每只怪兽即可。

从左往右遍历的时候,假设遍历到的这只怪兽,所在的坐标为X,血量为H,需要对其造成大于等于H点的伤害,所以需要使用技能的次数为⌈ H / A ⌉ (向上取整)。

因为每次需要选一个区间,这只怪兽左边的怪兽都被杀死了,所以我们可以把这只怪兽所再的位置作为区间起点,那么即对区间[ X , X + 2 ∗ D ] 的所有怪兽造成了⌈ H / A ⌉ ∗ A 的伤害。

因此在每次遍历到这只怪兽的伤害,需要判断它是否已经死亡了,所以需要查询单点位置加了多少。

需要动态区间加,单点查询,可以使用树状数组/分块/线段树。

考虑[ X , X + 2 ∗ D ] 的范围太大,无法作为数组下标,因此我们需要对其离散化。

代码:

#include<bits/stdc++.h>
using namespace std;

#define x first
#define y second 
#define pll pair<int,int>
#define int long long
const int N = 1e6 + 10 ;

int n,d,a,tr[N];
vector<pll> q ;
vector<int> v ;

int get(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int lowbit(int x)
{
    return x&-x ;
}
int sum(int x)
{
    long long res=0 ;
    for(int i=x;i;i-=lowbit(i)) 
        res+=tr[i] ;
    return res;
}
void add(int x,int c)
{
    for(int i=x;i<=N-10;i+=lowbit(i)) 
        tr[i] += c ;  
}

signed main()
{
    cin>>n>>d>>a ;
    for(int i=1;i<=n;i ++)
    {
        int l,r;
        scanf("%lld %lld",&l,&r) ;
        q.push_back({l,r}) ;
        v.push_back(l) , v.push_back(l+2*d+1);
    }
    
    sort(q.begin(),q.end());
    sort(v.begin(),v.end()); 
    v.erase(unique(v.begin(),v.end()),v.end()); 
    int ans=0;
    for(auto i:q)
    {
        int l=i.x,r=i.y;
        r=r+sum(get(i.x)); 
        if(r<=0)     continue; 
        int k=(r+a-1)/a;
        ans+=k; 
        add(get(i.x) , -k*a);
        add(get(i.x+2*d+1) , k*a);
    }
    cout<<ans;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值