问题 A: 阿克曼函数
题意:
给出阿克曼(Ackmann)函数定义,m,n定义域是非负整数(m≤3,n≤10),求函数值
![](https://img-blog.csdnimg.cn/img_convert/b2e963ff958086c8a7d50c47fb5cc242.png)
思路:模拟递归即可,注意边界条件
代码:
#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 个。现在用两个整数数组来表示命令串:
a1,a2,...am:一串将要被放进 Black Box 的元素。例如上面的例子中 a=[3,1,-4,2,8,-1000,2]a=[3,1,−4,2,8,−1000,2]。
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;
}