问题 A: [蓝桥杯2022初赛] 数位排序
题目描述
小蓝对一个数的数位之和很感兴趣,今天他要按照数位之和给数排序。
当两个数各个数位之和不同时,将数位和较小的排在前面,当数位之和相等时,将数值小的排在前面。
例如,2022 排在 409 前面,因为2022 的数位之和是6,小于 409 的数位之和13。
又如,6 排在 2022 前面,因为它们的数位之和相同,而 6 小于 2022。
给定正整数n,m,请问对 1 到 n 采用这种方法排序时,排在第 m 个的元素是多少?
样例输入
13
5
样例输出
3
思路分析
将前n个数按位数和排序,输出第m个即可
AC代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
//求位数和
int sw(int x)
{
int su=0;
while(x)
{
su+=x%10;
x/=10;
}
return su;
}
//排序规则
bool cmp(int x,int y)
{
if(sw(x)==sw(y))
return x<y;
return sw(x)<sw(y);
}
int main()
{
int m,n;
scanf("%d%d",&m,&n);
vector<int>ve;
for(int i=1;i<=m;i++)
ve.push_back(i);
sort(ve.begin(),ve.end(),cmp);
printf("%d\n",ve[n-1]);
}
问题 B: [蓝桥杯2018初赛]全球变暖
题目描述
你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
样例输入
7
.......
.##....
.##....
....##.
..####.
...###.
.......
样例输出
1
思路分析
用队列存储每一片岛屿,每次取出队头元素,存放与队头元素相邻的岛屿像素块,计算每片岛屿的像素块个数,同时判断该岛屿中每个像素块是否临海,若临海像素块等于该岛屿总像素块,则该岛屿会被淹没
AC代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
char c[1005][1005];
int vis[1005][1005]={0};//记录是否遍历过
//上下左右方位
int xx[4]={0,0,1,-1};
int yy[4]={1,-1,0,0};
int n;
int su=0;//被淹没岛屿个数
void dfs(int x,int y)
{
vis[x][y]=1;
queue<pair<int,int>>qu;
qu.push({x,y});
int su1=0,su2=0;//su1该岛屿像素块个数,su2该岛屿内与海洋相邻像素块个数
while(!qu.empty())
{
auto k=qu.front();//取对头
qu.pop();
int p=k.first,q=k.second;
su1++;
int f=0;
//判断四个方位像素块性质
for(int i=0;i<4;i++)
{
int X=p+xx[i];
int Y=q+yy[i];
if(X>=0&&X<n&&Y>=0&&Y<n)
{
if(c[X][Y]=='.') f=1;//与海洋相邻
else if(vis[X][Y]==0)//相邻的,未被访问过的岛屿像素块
{
vis[X][Y]=1;
qu.push({X,Y});
}
}
}
if(f) su2++;
}
if(su1==su2) su++;//岛屿会被淹没
return ;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>c[i][j];
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(c[i][j]=='#'&&vis[i][j]==0)
{
dfs(i,j);
}
}
}
cout<<su<<endl;
}
问题 C: 发工资
题目描述
假如每位老师的工资都知道,最少需要准备多少张人民币,才能在给每位老师发工资的时候都不用老师找零呢?
这里假设老师的工资都是正整数,单位元,人民币一共有100元、50元、10元、5元、2元、1元6种。
样例输入
3
1 2 3
0
样例输出
4
思路分析
因为要求人民币张数最少,所以尽可能发面值大的,工资对从大到小的面值依次向下取整再加和即可。
AC代码
#include<iostream>
#include<algorithm>
using namespace std;
int x[6]={100,50,10,5,2,1};//面值
int main()
{
int n;
cin>>n;
while(n!=0)//n==0标志输入结束
{
int su=0;
for(int j=0;j<n;j++)
{
int c;
cin>>c;
//对面值从大到小依次向下取整
for(int i=0;i<6;i++)
{
su+=c/x[i];
c=c%x[i];
}
}
cout<<su<<endl;
cin>>n;
}
}
问题 D: 看病要排队
题目描述
看病要排队这个是地球人都知道的常识。
不过经过细心的0068的观察,他发现了医院里排队还是有讲究的。0068所去的医院有三个医生(汗,这么少)同时看病。而看病的人病情有轻重,所以不能根据简单的先来先服务的原则。所以医院对每种病情规定了10种不同的优先级。级别为10的优先权最高,级别为1的优先权最低。医生在看病时,则会在他的队伍里面选择一个优先权最高的人进行诊治。如果遇到两个优先权一样的病人的话,则选择最早来排队的病人。
现在就请你帮助医院模拟这个看病过程。
输入
输入数据包含多组测试,请处理到文件结束。
每组数据第一行有一个正整数N(0<N<2000)表示发生事件的数目。
接下来有N行分别表示发生的事件。
一共有两种事件:
1:"IN A B",表示有一个拥有优先级B的病人要求医生A诊治。(0<A<=3,0<B<=10)
2:"OUT A",表示医生A进行了一次诊治,诊治完毕后,病人出院。(0<A<=3)
输出
对于每个"OUT A"事件,请在一行里面输出被诊治人的编号ID。如果该事件时无病人需要诊治,则输出"EMPTY"。
诊治人的编号ID的定义为:在一组测试中,"IN A B"事件发生第K次时,进来的病人ID即为K。从1开始编号。
样例输入 复制
7
IN 1 1
IN 1 2
OUT 1
OUT 2
IN 2 1
OUT 2
OUT 1
2
IN 1 1
OUT 1
样例输出 复制
2
EMPTY
3
1
1
思路分析
定义三个set容器,分别对应三个窗口,set排序规则为先按优先级降序排列,再按序号升序排列,每次某个窗口医生诊治时,诊治对应容器最开始的那个病人。
AC代码
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
class popo
{
public:popo(int X,int Y)
{
x=X;
y=Y;
}
int x;
int y;
};
//自定义排序,先按优先级从大到小排,再按序号从小到大排
class cmp
{
public:bool operator()(const popo &p,const popo &q)
{
if(p.x==q.x)
return p.y<q.y;
return p.x>q.x;
}
};
int main()
{
int n;
while(cin>>n)
{
multiset<popo,cmp>se[5];//set容器,可以在放入元素时做到实时排序
string s;
int a,b;
int k=1;
for(int i=0;i<n;i++)
{
cin>>s;
if(s=="IN")
{
cin>>a>>b;
se[a].insert({b,k});//a窗口放入一个优先级为b,序号为k的病人
k++;
}
else
{
cin>>a;
if(se[a].empty())//该窗口没有病人
cout<<"EMPTY"<<endl;
else
{
cout<<se[a].begin()->y<<endl;//输出病人序号
se[a].erase(se[a].begin());//移除病人
}
}
}
}
}
问题 E: 6.3.2.1 电话网络
题目描述
电话公司正在创建一个新的电话网络,每个地方都有一个电话交换机(编号为1~N)。线路是双向的,并且总是将两个地方连接在一起,在灭个地方,线路都终止与电话交换机。从每个地方都可以通过线路达到其他地方,但不需要直接相连,可以进行多次交换。有时候在某个地方发生故障,会导致交换机无法运行。在这种情况下,除了无法到达失败的地方,还可能导致其他地方无法连接。这个地方(发生故障的地方)是至关重要的。请写程序来查找所有关键位置的数量。
样例输入
5
5 1 2 3 4
0
6
2 1 3
5 4 6 2
0
0
样例输出
1
2
思路分析
额……这道题写着好心虚……说实话,我没太看懂题……但糊里糊涂就AC了。我的理解是对于每组样例的直达关系,如果某个点可以以他为起点直达其他一个或多个点,也就是说画成树后的非叶子节点,那么他就是关键节点。(还是去看其他同学的题解吧,总感觉我的理解不太对)
AC代码
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
int main()
{
int n;
while(cin>>n&&n)
{
int su=0;
int x;
while(cin>>x&&x!=0)
{
int p=0;
while(getchar()!='\n')
{
int y;
cin>>y;
p++;
}
if(p) su++;//有以该节点为起点的可以直达的其他节点
}
cout<<su<<endl;
cin>>n;
}
}
问题 F: 7.1.4.1 重型运输
题目描述
你会得到城市的平面图,由十字路口之间的街道(有重量限制)描述,编号从1到n。你的任务是找到从1 (Hugo的位置)到n(客户的位置)可以运输的最大重量。你可以假设至少有一条路径。所有的街道都可以双向通行。
样例输入
1
3 3
1 2 3
1 3 4
2 3 5
样例输出
Scenario #1:
4
思路分析
运用Dijkstra算法,求每个节点到源点的所有路径中最小值的最大值。每次以与源点距离最大值更新其他未确定结果的节点。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#define int long long
using namespace std;
int a[1005][1005];//存边
int dis[1005];//到第i个点的最大承受重量
int vis[1005];//该点结果是否已经确定
signed main()
{
int t;
cin>>t;
for(int o=1;o<=t;o++)
{
int n,m;
cin>>n>>m;
memset(a,0,sizeof a);
memset(dis,0,sizeof dis);
memset(vis,0,sizeof vis);
while(m--)
{
int u,v,w;
cin>>u>>v>>w;
a[u][v]=max(a[u][v],w);
a[v][u]=max(a[v][u],w);
}
//假设以1为源点
for(int i=1;i<=n;i++)
dis[i]=a[1][i];
int su=1;//su-已经确定的点的个数
int mi;//mi-每次循环没确定的点中的最大值
int d;//记录最大值对应的位置
vis[1]=1;
while(su<n)
{
mi=-1,d=-1;
for(int i=1;i<=n;i++)
{
if(vis[i]==0&&mi<dis[i])
{
mi=dis[i];
d=i;
}
}
su++;
vis[d]=1;
for(int i=1;i<=n;i++)
{
if(vis[i]==0)
{
dis[i]=max(dis[i],min(dis[d],a[d][i]));//更新其余点的结果,所有路径中每条路径的最小值的最大值
//dis[d]-d与源点可允许通行的最大货物量,a[d][i]-d与i之间可允许通行的最大货物量
}
}
}
cout<<"Scenario #"<<o<<":"<<endl;
cout<<dis[n]<<endl;
cout<<endl;
}
}
问题 G: 打怪兽version-3
题目描述
有N只怪兽的血量为Hi
你现在有2个技能
技能1:选择一个怪兽i,使其血量降低1点
技能2:选择一个怪兽i,使其血量变为0
问你需要使用多少个1技能可以杀死所有怪兽(其血量小于等于0即为死亡)
特别的,技能2只能使用K次
样例输入
3 1
4 1 5
样例输出
5
思路分析
前min(n,k)个血量大的怪兽用技能2消灭,其余的用技能1消灭,即使用技能1的数量为其余怪兽血量总和。注意k可能会大于n。
#include<iostream>
#include<algorithm>
#include<set>
#define int long long
using namespace std;
int a[200005];
signed main()
{
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++)
cin>>a[i];
sort(a,a+n);
int su=0;
n-=min(n,k);
for(int i=0;i<n;i++)
su+=a[i];
cout<<su<<endl;
}
问题 H: 2.4.1 间谍
题目描述
X 国的情报委员受到一份可靠的消息,信息表明 Y 国将派间谍去窃取 X 国的机密文件。X 国指挥官手中有两份名单列表,一份是 Y 国派往 X 国的间谍名单列表,另一份是 X 国以前派往 Y 国的间谍名单。这两份名单列表可能有些重叠。因为间谍可能同时扮演两个角色,称之“双重间谍”。因此,Y 国可以把双重间谍送回 X 国。很明显,这对 X 国是有利的,因为双重间谍可以把 Y 国的机密文件带回,而不必担心在 Y 国边境被拘留。所以指挥官决定抓住由 Y 国派出的间谍,让普通人和双重间谍进入。那么你能确定指挥官需要抓捕的间谍名单吗?
样例输入
8 4 3
Zhao Qian Sun Li Zhou Wu Zheng Wang
Zhao Qian Sun Li
Zhao Zhou Zheng
2 2 2
Zhao Qian
Zhao Qian
Zhao Qian
样例输出
Qian Sun Li
No enemy spy
思路分析
暴力寻找在名单A,B中但不在名单C中的人。
AC代码
#include<iostream>
#include<algorithm>
#include<set>
#define int long long
using namespace std;
signed main()
{
int a,b,c;
string A[505],B[505],C[505];
while(cin>>a>>b>>c)
{
int su=0;
for(int i=0;i<a;i++)
cin>>A[i];
for(int i=0;i<b;i++)
cin>>B[i];
for(int i=0;i<c;i++)
cin>>C[i];
for(int i=0;i<a;i++)
{
for(int j=0;j<b;j++)
{
if(A[i]==B[j])
{
int f=0;
for(int k=0;k<c;k++)
{
if(A[i]==C[k])
{
f=1;
break;
}
}
if(f==0)
{
su++;
cout<<A[i]<<" ";
}
}
}
}
if(su==0)
cout<<"No enemy spy";
cout<<endl;
}
}
问题 I: 2.4.6 黑盒子
题目描述
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]。
- u1,u2,⋯,un:表示第 u_iui 个元素被放进了 Black Box 里后就出现一个 GET 命令。例如上面的例子中 u=[1,2,6,6] 。输入数据不用判错。
样例输入
7 4
3 1 -4 2 8 -1000 2
1 2 6 6
样例输出
3
3
1
2
思路分析
每次排序前ui个元素,输出第j个。
AC代码
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int a[200005],b[200005];
int main()
{
int m,n;
cin>>m>>n;
for(int i=0;i<m;i++)
cin>>a[i];
int j=0;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
sort(a,a+x);
cout<<a[j]<<endl;
j++;
}
}
问题 J: 2.4.7 集合运算
题目描述
给定N个集合,第1个集合Si有Ci个元素(集合可以包含两个相同的元素)。
集合中的每个元素都用1~10000 的正数表示。
查询两个给定元素i和j是否同时属于至少一个集合。
换句话说,确定是否存在一个数字k(1≤k≤N),使得元素i和元素j都属于Sk。
样例输入
3
3 1 2 3
3 1 2 5
1 10
4
1 3
1 5
3 5
1 10
样例输出
Yes
Yes
No
No
思路分析
用bitset实现,bt[x]中第i位表示第i个集合中是否有x,再通过取&判断两个数是否在某同一个集合中。
AC代码
#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
bitset<1000>bt[10005],c;
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
int m;
cin>>m;
for(int j=0;j<m;j++)
{
int x;
cin>>x;
bt[x].set(i);
}
}
int q;
cin>>q;
for(int i=0;i<q;i++)
{
int x,y;
cin>>x>>y;
c=bt[x]&bt[y];
if(c.any()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
问题 K: 打怪兽version-4
题目描述
有1只怪兽的血量为H
你现在有一个技能
你可以选择一个怪兽,假设它当前生命值为x
对其造成伤害之后,该怪兽会分裂成2个生命值为⌊x/2⌋的怪兽
问你需要使用多少次技能可以杀死所有怪兽
样例输入
2
样例输出
3
思路分析
若f[i]表示打败血量为i的怪兽需要使用的技能次数,则f[i]=f[i/2]*2+1,但是直接写的话会TLE,但其实就相当于每隔2的整数次幂为前一个整数次幂的2倍加1
AC代码
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
signed main()
{
int n;
cin>>n;
int su=0;
int i=0;
int p=1;
while(i<n)
{
su=su*2+1;
i+=p;
p*=2;
}
cout<<su<<endl;
}
问题 L: 打怪兽version-5
题目描述
有一只怪兽的血量为H
你现在有N个技能
技能i可以对怪兽造成Ai点伤害,但是需要Bi点魔力值
问你最少需要多少点魔力值,可以杀死该怪兽(其血量小于等于0即为死亡)
每个技能可以重复使用
样例输入
9 3
8 3
4 2
2 1
样例输出
4
思路分析
好累呀,真的不想再打怪兽了,就不能和平共处吗……
类似于完全背包问题,不过传统背包问题是想让价值最大,本题是想让耗费魔力值最少。用f[i]表示消灭血量i需要的最少魔力值。所以赋初值时f[0]=0,其余皆为无穷大。若遍历到第j个技能,可得递推关系为,若i>=a[j],f[i]=min(f[i],f[i-a[j]]+b[j]),否则f[i]=min(f[i],f[0]+b[j])
AC代码
#include<iostream>
#include<algorithm>
#include<string.h>
#define int long
using namespace std;
int f[100005];//f[i]表示消灭血量i需要的最少魔力值
int n,h;
int a[10005],b[10005];
signed main()
{
cin>>h>>n;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=h;i++)
{
for(int j=1;j<=n;j++)
{
if(i>=a[j])
f[i]=min(f[i],f[i-a[j]]+b[j]);
else
{
f[i]=min(f[i],f[0]+b[j]);
}
}
}
cout<<f[h]<<endl;
}
问题 M: 打怪兽version-6
题目描述
有N只怪兽,每只怪兽都有一个坐标Xi,和其血量Hi
你有一个技能
每次技能你可以选择一个坐标x
对区间[x-D,x+D]的所有怪兽造成伤害A点
问你最少需要使用多少次技能可以杀死所有怪兽(其血量小于等于0即为死亡)
样例输入
3 3 2
1 2
5 4
9 2
样例输出
2
思路分析
从左边开始消灭怪兽,则该点前面的怪兽都被消灭了,以该点为区间左端点消灭怪兽,若怪兽血量已经为0,则跳过。又由于整个区间搂到了1e9,不能直接用数组模拟数轴,所以需要先进行离散化处理。
怪兽终于要打完了哈哈哈哈哈
AC代码
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
#define int long long
using namespace std;
vector<int>ve;
vector<pair<int,int>>v;
int f[1000005]={0};
int n,d,a;
//x在ve中的位置
int get(int x)
{
return lower_bound(ve.begin(),ve.end(),x)-ve.begin()+1;
}
//离散化
int lo(int x)
{
return (x&(-x));
}
//前缀和
int sum(int x)
{
int su=0;
for(int i=x;i;i-=lo(i))
su+=f[i];
return su;
}
void add(int x,int y)
{
for(int i=x;i<=1000000;i+=lo(i))
{
f[i]+=y;
}
}
signed main()
{
cin>>n>>d>>a;
int su=0;
d=2*d+1;
for(int i=0;i<n;i++)
{
int x,h;
cin>>x>>h;
ve.push_back(x);
ve.push_back(x+d);
v.push_back({x,h});
}
sort(v.begin(),v.end());
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());//去重
for(auto k:v)
{
int p,q;
p=k.first;q=k.second;
q=q+sum(get(p));
if(q<=0) continue;
int t=(q+a-1)/a;
su+=t;
add(get(p),-t*a);
add(get(p+d),t*a);
}
cout<<su<<endl;
return 0;
}