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 AZ,第 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(people1,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 Q1张牌,最多补充 m i n ( a [ i ] , Q − 1 ) min(a[i], Q−1) min(a[i],Q1) 张牌。

所以可以按照 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+yn)

假如公司没有一直招聘新员工,请问 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[i1][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[i1][j1]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[i1][j]+dp[i1][j1]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<typekey,typevalue>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;
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值