8.21
突然心血来潮,想写一个博客来记录这一次比赛。
前言
这次比赛打的挺爽的。
其实前三道题都不难。我8:00开考时,在9:00左右就把前三道基本都码出来了。
然后第四题和第五题让我见祖宗了。
只不过这次我比前几次打比赛的时候都稳了一些,知道第五题我做不出来,就费了很长时间去勘误前三道题,果不其然,前三道题都拿了满分,成功RANK2(和RANK1那个beyond就差20分)
然后这就是我比赛的时候的真实感受了!:
怎么说。
还是得出来道个歉的。
Klz、Czl、Czy、Mmz、Lyr,哇!!我害了你们啊!!我不应该在前面的注释里毒奶你们的!!!
果然,我的毒奶功力很强的,在注释里面点到名的基本上都崩了。。
#define KlzhNB 666666 //钊哥祝我AK
#define CzlNB 666666 //良哥保我数据结构
#define CzyNB 666666 //玉哥保佑我算法
#define ZsmNB 666666 //铭哥保佑我模拟
#define MmzNB 666666 //马哥保佑我能想到马氏数学做法
进入正题。
T1 面试
知名跨国大公司蜗牛集团的的蜗牛老师要对求职者进行面试,面试总共分四轮,每轮的面试官都会对面试者的发挥进行评分。评分有 A A A B B B C C C D D D四种。如果面试者在四轮中有一次发挥被评为 D D D,或者两次发挥被评为 C C C,就不会通过面试。如果面试者没有一次被评为 D D D,并且有三个或以上的 A A A,则会获得 s p e c i a l o f f e r special~offer special offer。其余情况会获得普通 o f f e r offer offer。
现在告诉你一些面试者的发挥,请你算一算,他们的面试结果分别是什么。
输入格式
第一行输入一个
T
T
T,代表面试者的个数。
接下来有
T
T
T行,每行都有一个长度为
4
4
4的字符串,每个位置的字符分别代表面试者每一轮的发挥。
输出格式
输出
T
T
T 行,分别表示
T
T
T个面试者的面试结果。如果面试失败,输出
f
a
i
l
e
d
failed
failed,如果面试通过,但不是
s
p
e
c
i
a
l
o
f
f
e
r
special~offer
special offer,则输出
o
f
f
e
r
offer
offer,否则输出
s
p
o
f
f
e
r
sp~offer
sp offer。
样例
输入样例1
2
AAAB
ADAA
输出样例1
sp offer
failed
数据范围与提示
对于 100% 的数据,T≤10^3 。
分析
签到题,不多谈!
只用记录下
A
A
A、
C
C
C、
D
D
D的个数。因为
B
B
B在这里是没有用的。不参与任何判断。
然后要看准题!!
MMZ因为把题看错了丢100分。
LYR因为没换行丢了100分。
MYP因为数组开小丢了100分。
这些是致命伤,以后这种错误千万一定不能犯。
还有一些是因为没有清空计数器丢了100分,这也是很不应该的错误,这证明在思考的方式上出现了问题。
那么怎么规避这些问题呢?
我想出了一种方法。(军哥如果你在看的话,我觉得这个很有用)
编出来样例的所有情况,自测保平安!
有人说,这不就是乱打么?
不!乱打我们也要打的有效率一点。
首先,样例要过。
然后,自己看准题意编数据,
如:我们先来看,sp offer需要什么条件呢?三个A及以上对吧,ABAA、AABA、AAAB、BAAA
并且还不能有D,如果有D需要进入failed,那么我们就试AAAD、DAAA、ADAA、AADA。
其他情况,就按照offer来模拟,AABC、ADCC、ACDD…
有些人估计会问,一个签到题下这么大功夫至于么?
至于!我们搞科学的,就是要精细、精细、还是TMD精细!不要提出这种Sometimes naive的问题,多提升知识水平。
核心代码:
for(;n--;){
string s;
cin>>s;
long long suma=0,sumb=0,sumc=0,sumd=0;
for(int i=0;i<s.size();i++){
if(s[i]=='A'){
suma++;
}
if(s[i]=='C'){
sumc++;
}
if(s[i]=='D'){
sumd++;
}
}
if(sumd>0 || sumc>=2){
cout<<"failed"<<endl;
}
else if(suma>=3){
cout<<"sp offer"<<endl;
}
else{
cout<<"offer"<<endl;
}
}
T2 Excel计数法
Excel的表格中,前
26
26
26 列的编号为
A
−
Z
A-Z
A−Z,第 27 列为
A
A
AA
AA,然后是
A
B
,
A
C
,
.
.
.
,
A
Z
,
B
A
,
.
.
.
,
Z
Z
,
A
A
A
,
.
.
.
AB, AC, ..., AZ, BA, ..., ZZ, AAA, ...
AB,AC,...,AZ,BA,...,ZZ,AAA,... 现在你需要写一个程序,计算某列在 Excel中的编号。
输入:1 输出:A
分析
看着挺简单的,其实上手做不知道从何做起。
不过LYR大佬强势保佑有思路,我仔细想了想之后发现很水。
其实还是进制转换,只不过是26进1罢了。
我的思路是把26个字母放在一个字符串里,需要哪个取哪个,非常方便。
然后再开一个字符串,把判断后的存进去,一定要先开空串要不然会出问题。
代码老短了。
string all="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string s="";
long long n;
cin>>n;
for(;n>0;){
n--;
s=all[n%26]+s;
n=(n-n%26)/26;
}
cout<<s<<endl;
然后就这么奇短的代码上课被军哥吐槽了。。说挺丑的。。。
确实啊,这么一说好像是比军哥的丑一点。
但是能A就行。
这是军哥的核心代码:
while(n) {
int x = n%26;
n = n/26;
if(x == 0) {
ans += 'Z';
n--;
}
else ans += (x-1+'A');
}
最后我提一嘴,这一题一开始我没想到解法的时候想到的是打表。
确实打表能立大功,但是我打着打着发现好像有个可以满分的做法,那就干脆这样试了!
T3 纸牌游戏
题目描述
知名跨国大公司蜗牛集团的员工们在一起玩纸牌游戏,规则如下:
总共有
n
n
n个人,每个人初始有
n
n
n张牌。每一轮从第一个人开始轮流操作,第
i
i
i个人每次操作必须选择
m
i
n
(
p
e
o
p
l
e
−
1
,
a
i
)
min(people−1, a_i)
min(people−1,ai)个不同的人,分别从他们手中拿走一张牌给自己。 其中
p
e
o
p
l
e
people
people 为游戏现存人数,手上没有牌的人立即被淘汰出局。
大家希望有尽可能多的人出局,游戏无限的进行下去,问最终游戏中最少还有几个人没有出局。
注意:不能从自己手中拿牌。
输入格式
第一行输入一个数字
n
n
n ,代表游戏的总人数。接下来输入
n
n
n个数字,分别代表
a
i
a_i
ai 。
输出格式
输出一行一个整数表示游戏最终最少剩几个人。
输入样例1
2
1 2
输出样例1
2
样例解释1
两个人只能互相拿对方一张牌给自己,游戏永远进行下去。
分析
考场上的思路:
简单的贪心。
要淘汰掉多的人,所以就要去把最少牌的人一个一个拿完。
单调递增排一遍,先去拿第一个人的牌
设这个人是i个人,则n-i个人能拿他的牌。
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
if(a[i]>=n-i){
cout<<n-i+1<<endl;
break;
}
}
return 0;
}
军哥的论证:
其实我没有严格的证明我贪心的正确性,只能说是瞎猫碰上死耗子了。
军哥的证明确实很严谨。
先给出结论: 在现存 Q 个人的情况下, 对于第 i个人,如果a[i]<Q−1 ,那么 第i个人必然会被淘汰出局,反之则永远不会被淘汰出局。
证明:
因为在每一轮游戏中,第 i i i个人必然会损失掉 Q − 1 Q−1 Q−1张牌,最多补充 m i n ( a [ i ] , Q − 1 ) min(a[i], Q−1) min(a[i],Q−1) 张牌。
所以可以按照 a [ ] a[] a[] 从小到大排序,对于永远不会踢出的第 i i i 个人而言,第 i + 1 i+1 i+1 到 第 n n n 个人也必然不会被踢出局。
a [ i ] a[i] a[i] 越小越容易被淘汰。
T4 涨薪
知名跨国大企业蜗牛集团中总共 n n n 个人,其中第 i i i个人的初始工资为 a i a_i ai。公司根据每个人的绩效(工作表现)来评定每个人的涨薪幅度。每年有 x x x个人绩效为 A A A ,工资可以变为原来的 3 3 3 倍; y y y 个人绩效为 B B B,工资可以变为原来的 2 2 2倍,其余人绩效为 C C C,工资不变,连续两年绩效为 C C C会被开除。(保证 x + y ≤ n x+y≤n x+y≤n)
假如公司没有一直招聘新员工,请问 m m m年后,公司需要给所有在职员工支付的工资总和最多为多少。由于答案可能很大,请输出对 1 0 9 + 7 10^9+7 109+7 取模后的结果。
输入格式
输入第一行包含四个正整数
n
,
m
,
x
,
y
n, m, x, y
n,m,x,y,意义如题面所示。
接下来一行包含 n n n个正整数,第 i i i个正整数为 a i a_i ai 代表第 i i i 个人的初始工资。
输出格式
输出一行一个整数表示
m
m
m年后工资总和对
1
0
9
+
7
10^9+7
109+7取模后的结果。
分析
我**!!这题我本来能拿90!!!结果快速幂少%100直接变成0分。我直接原地爆炸蹦蹦飞天**。
唉,我的想法跟正解非常接近。
贪心思路:
最强的员工涨薪
一开始的工资单调递减排序,因为1-x员工涨m次3倍工资,x+1-x+y涨m次2倍工资
不用快速幂直接见祖宗。 必须用快速幂优化。
核心代码(90pts,第一个样例没过,军哥可以帮我看看,或者有没有神仙帮我看看也中):
(2021.8.22更新,评论区的Fxr哥帮我指出了10pts的错误,是因为我没有判断m为1时候还有加上C类工资。)
long long kuaisumi(long long k){
long long p=m,ans=1;
while(p){
if(p&1){
ans=(ans*k)%mod;
}
k=(k*k)%mod;
p>>=1;
}
return ans%mod;
}
bool mycmp(int a,int b){
return a>b;
}
void beforex(){
for(int i=1;i<=x;i++){
sum=sum+(a[i]*kuaisumi(3)%mod)%mod;
}
}
void afterx(){
for(int i=x+1;i<=x+y;i++){
sum=sum+(a[i]*kuaisumi(2)%mod)%mod;
}
}
int main(){
cin>>n>>m>>x>>y;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n,mycmp);
beforex();
afterx();
cout<<sum%mod<<endl;
return 0;
}
T5 富有数
题目描述
知名跨国公司蜗牛集团的蜗牛老师很喜欢
6
,
8
6, 8
6,8 这样的数字。 蜗牛老师称一个数为 富有数,当且仅当这个数在十进制下的每一位都是
6
6
6 或者
8
8
8 。
我们成一个子序列为 富有序列,当且仅当这个子序列的每一个数都是富有数,并且其中没有任何一对 富有数是相同的。
显然,一个空的子序列也是一个 富有序列。
现在蜗牛老师得到了一个序列,她想知道这个序列中所有是 富有序列的子序列的长度和。
输入格式
第一行一个整数
n
n
n表示序列长度。
第二行 n n n 个整数表示这个序列。
输出格式
输出一行一个整数,表示所有 富有序列 的长度和。
由于答案很大,你只需要输出答案对 998244353 998244353 998244353 取模的结果。
样例
输入样例1
5
68 68 5 688 867
输出样例1
7
样例解释1
有 {},{1},{2},{4},{1,4},{2,4} 五个子序列合法,总长度为
7
7
7。注意 {1,2}是不合法的因为存在两个相同的数字
分析
打死我都想不到DP。。。
设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i种富有数里面选 j j j个的方案数。
初始状态: d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1
状态转移:
不用第i种 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i−1][j] dp[i][j]=dp[i−1][j];
用第i种, d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] ∗ t i dp[i][j]=dp[i−1][j−1]∗t_i dp[i][j]=dp[i−1][j−1]∗ti;
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] ∗ t i dp[i][j]=dp[i−1][j]+dp[i−1][j−1]∗t_i dp[i][j]=dp[i−1][j]+dp[i−1][j−1]∗ti
唉。。。我真是太垃圾了。。
比赛总结
虽然Rank2,但是可惜第四题细节没注意,要不然稳稳的第Rank1。哭了。
不过这次最大的进步在于,我居然稳了一回,简单的题没有失掉分!
MAP及用法
MAP是STL(标准模板库)里的一种容器。
其核心功能是键值对。
键值对,就是一个键对一个值的函数映射关系。可以有很多键对应一个值,但是不能有一个键对应多个值。即初中函数老师经常会讲的:“一个孩子有一个妈,但妈有几个孩子不一定”。
m
a
p
<
t
y
p
e
−
k
e
y
,
t
y
p
e
−
v
a
l
u
e
>
m
p
map<type- key,type-value> mp
map<type−key,type−value>mp
这就是map的声明方法,这个type是数据类型。
m
a
p
<
i
n
t
,
s
t
r
i
n
g
>
s
map<int ,string> s
map<int,string>s 这样也可以
m
a
p
<
p
a
i
r
<
i
n
t
,
i
n
t
>
,
v
e
c
t
o
r
<
i
n
t
>
>
m
map<pair<int,int>,vector<int> >m
map<pair<int,int>,vector<int>>m 这样也可以。
不过,如果有像vector这种有几个元素不一定的情况,不能放到键里面。
并且,如果键是pair、map,也算一个键。
其实可以理解为,map就是一个键值对数组。(其实有一个更加贴合的名字,名曰"容器“)
迭代器:迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
用法:
m
a
p
<
l
o
n
g
l
o
n
g
,
i
n
t
>
:
:
i
t
e
r
a
t
o
r
i
;
/
/
定
义
一
个
名
字
为
i
的
迭
代
器
map<long~long, int>::iterator~i;// 定义一个名字为 i 的迭代器
map<long long,int>::iterator i;//定义一个名字为i的迭代器
MAP的存储
void count()
{
for(int i=1; i<=n; i++) {
mp[a[i]]++; // logn。mp[a[i]]表示a[i]为键对应的值。
}
}
MAP的遍历
需要用到迭代器
map<long long, int>::iterator i; // 定义一个名字为 i 的迭代器
for(i=mp.begin(); i!=mp.end(); ++i) { // 默认按照键升序来排序
printf("%lld %d\n", i->first, i->second); // i表示一个键值对的指针
}
其它MAP的操作工具
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数
MAP的应用
【实例】用map统计字符串出现的次数
给定n个字符串,m个问题,每次问题询问一个字符串出现的次数。
map<string,int> mp;
char s[25];
for(int i=1;i<=n;i++){
scanf("%s",s);
mp[s]++;
}
for(int i=1;i<=m;i++){
scanf("%s",s);
if(mp.find(s)==mp.end()) cout<<0<<endl;
else{
printf("%d\n",mp[s]);
}
}
比赛的T5?
照样也是可以用map解决的。
map<long long, int> mp; // 定义一个键为long long类型,值为 int类型的map容器
void count()
{
for(int i=1; i<=n; i++) { // 键是富有数这个数字,值是这个富有数的个数
mp[a[i]]++; // 修改也是log的
}
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; ++i)
scanf("%lld", &a[i]);
count();
printf("%d\n", mp[a[1]]); // mp[a[i]] 表示 a[i]为键对应的值 ,每次查找都是log(n)的
// 输出所有的a[i]与其对应的个数,遍历所有的键
map<long long, int>::iterator i; // 定义一个名字为 i 的迭代器
for(i=mp.begin(); i!=mp.end(); ++i) { // 默认按照键升序来排序
printf("%lld %d\n", i->first, i->second); / i表示一个键值对的指针
}
return 0;