8.14 模拟赛

一次可爱的模拟赛??

(呜呜呜,本来oj上写好了的,一个手残全删了)

直接写题解吧

似乎应该大概好像可能有点草率

T1 蜗牛老师的成绩统计

   大概题意就是:n个人比赛跑步, 每个人到达终点有一个时间( 一些hour , 一些minute, 一些second)   问你他们的排名顺序?

显然,很水的题,用结构体sort就能A

bool mycmp(people x,people y)
{
	return x.hour<y.hour||
	      (x.hour==y.hour&&x.minute<y.minute)||
	      (x.hour==y.hour&&x.minute==y.minute&&x.second<y.second);
} 

------------------------------------------------------------------------------------------------------------------------(别看我,我是分割线)

T2 小蜗牛的逃离

这是一道比较恶心的贪心,这里就详细讲下,顺便发表一下本人一些想法 (有些部分可能会引起不适

  题意:一只弱小,可怜,无助的蜗牛要在 n 天之内收集食物,初始时它有一个采集能力 X ,  
  
  之后的每天它可以选择:  [采集获得当前 X 值的食物] 或者 [锻炼身体让 X 的值增加 1 ] 
  
  第i天,它必须吃掉 a[i] 的食物;否则,它就狗带了
  
  问你 n 天之后小蜗牛最多能收集到多少食物,如果它难逃狗带的命运,输出“-1”

看完第一反应想着:哟,是道贪心啊!好的,太好了,那我就写dfs吧 [doge][doge]

再一看数据范围,噫!就这,就这,就这才给 30 30 30 分?emmm,算了,先这么弄吧

然后就忐忑地码起来了…

每一天都可以选择 采 或 不采 ,因此时间复杂度为 O ( t ∗ 2 n ) O(t*2^n) O(t2n)

void dfs(int day,long long x,long long now)//当前到第 day 天,采集能力为 x ,共采集了 now (个?吨?堆?)食物
{
	if(now<a[day]){
		return;
	}//当前 now 值达不到所要吃的食物数,小蜗牛就狗带了,return
	else now-=a[day]; 
	if(day==n){
		ans=max(ans,now);//到达边界,更新答案
		return;
	}
	
	dfs(day+1,x+1,now);//采集
	dfs(day+1,x,now+x);//不采集
}

其他的做完以后,又回来看这道题,想试着推一下:

首先我在想:如果小蜗牛每天不用吃东西,那么最优值应该怎么求呢?

设 小蜗牛开始时采集能力为 X ,在接下来的 n 天中 ,锻炼 y 天

1.首先,我们一定要要把这 y 天往前放,也就是前 y 天锻炼 ( 第 i 天锻炼,第 i+1 天采集 显然优于 第 i 天采集,第 i+1 天锻炼)

这样,我们有 前 y 天锻炼,n-y 天采集

由此,可以得出: 能采集到的食物数 = ( n − y ) ∗ ( X + y ) =(n-y)*(X+y) =(ny)X+y

仔细观察会发现: ( n − y ) + ( X + y ) = n + X (n-y)+(X+y)= n+X ny+X+y=n+X 它们的和是一个定值 !! ohohohohohohohohoh

因此就能用到小学老师教的: 和一定,差越小,积越大 ( 具体证明大佬们可以发评论区🙏🙏)

作差,得:abs(( n - y )-( X + y ))= abs(n - X - 2y )> = 0 因此最小值为 0

这样的话,我们就能得出 y = ( n - X ) / 2 ,因为 X 可能会大于 n ,我们要和 0 取个 max

综上: y = max( ( n - X ) / 2 ,0)(至于向上还是向下取整就无所谓了,毕竟 a ∗ ( a + 1 ) = ( a + 1 ) ∗ a a * ( a + 1 ) = ( a + 1 )*a a(a+1)=(a+1)a

在任意 n 天里:前 y 天中,锻炼 > 采集;从 y+1 到第 n 天,采集>锻炼

然后我们把每天要吃东西的情况考虑进去,

先思考前 y 天

1.如果 当前剩余的食物 now >= a[i](今天所要吃的食物),那么 now-=a[i], 今天选择锻炼

2.否则 当前食物不够吃,那么今天就要采集 ,如果采集后食物够了,同理1.

如果仍不够,那么昨天就要采集,如果采集后食物够了,同理1.

否则,前天…

显然,我们需要找到第 k 天,要保证 k 尽可能的大(即采集天数i-k+1尽可能小)&& 从第 k 天到第 i 天全部采集食物能够满足a[i]

由此,我们引出(老刘独家题解法(老刘nb6666)):

从第 i 天(缺食物的那天)开始倒着往前 枚举 撤销每一天,如果在第 k 天时,食物够了,那么 k 就一定是最大值

细节很多啊!

至于后 y+1~n 天,

无论怎样都是要采集的,只需判断食物是否>=a[i]即可。

最后,由于每次枚举都会改变 n ,X 的值,我们需要实时更新 y

此时y的意义就是:当前采集能力为X,在剩下 n 天中,前 y 天锻炼,y+1~n 天采集

当 y=0 时,即 当前采集能力为X,在剩下 n 天中,前 0 天锻炼,1~n 天采集,也就是全部采集喽

“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”

总结一下:从第一天开始枚举,当 y ! = 0 时,尽可能锻炼,如果食物不够吃,往前撤销每一天;

y = 0 后,每天都要采集,判断食物是否够吃。最后输出即可

(害,本人去码这道题时就剩下20min了,慌了慌了,时间没有把握好)

czl,czy 大佬现场A的代码,%%%%%%%

------------------------------------------------------------------------------------------------------------------------(别看我,我是分割线)

T3 蜗牛老师研究基因

(题意就不说了,三千个字了,呜呜,手疼)

也是道很水的题,开一个 f 数组,标记每一个位置( 0~m-1 )易感人群的基因

再去扫描不易感的人群这个位置的基因,如果没有重叠,ans++

for(int i=0;i<m;i++){//水
		f[1]=f[2]=f[3]=f[4]=0;
		for(int k=1;k<=n;k++){
			if(str1[k][i]=='A') f[1]=1;
			if(str1[k][i]=='C') f[2]=1;
			if(str1[k][i]=='G') f[3]=1;
			if(str1[k][i]=='T') f[4]=1;
		}
		bool ff=0;
		for(int k=1;k<=n;k++){	
			if(str2[k][i]=='A'&&f[1]==1) ff=1;
			if(str2[k][i]=='C'&&f[2]==1) ff=1;
			if(str2[k][i]=='G'&&f[3]==1) ff=1;
			if(str2[k][i]=='T'&&f[4]==1) ff=1;
		}
		if(ff==0) {
			ans++;
		}
	}

------------------------------------------------------------------------------------------------------------------------(别看我,我是分割线)

T4 蜗牛老师的代码

这是一道比较有趣的分治题目,我第一次看这道题时感觉式子不太好推,一看数据暴力分给了50,就先码了

	cin>>str;
	cin>>t;
	for(int i=1;i<=t;i++){
		cin>>q[i];
		maxx=max(maxx,q[i]);
	}
	while(str.size()<maxx){//创建一个长度为所有查询中的最大值的字符串
		x="";
		x=x+str[str.size()-1]+str.substr(0,str.size()-1);//x即为旋转后的字符串
		str=str+x;
	}

	for(int i=1;i<=t;i++){
		cout<<str[q[i]-1]<<endl; 
	}

显然,如果查询达到了 1 0 18 10^{18} 1018,字符串是会炸掉的,因此我们肯定不能创建出那么长的字符串

我的想法:首先给字符串赋上一个 “等级” :0级字符串为初始长度,1级为2 * 0级的长度,2级为2 * 1级的长度…以此类推

一般做分治题我都有个习惯,在纸上写一个(以这道题为例):

f ( n , i ) 表示 要查询在 第 n 级 字符串中,下标为 i 的字符,str表示初始字符串, len[i]表示第 i 级字符串的长度

那么 f( n , i )={

  1. str [ i - 1 ]; 。。。。。。( n==0 )
  2. f ( n-1, i ) ; 。。。。。。( i<=len[n-1] )
  3. f ( n-1,len[n-1] )。。。。。(i==len[n-1]+1)
  4. f( n-1,i-len[n-1]-1)。。。。(i>len[n-1]+1)
    }

这样的式子列出来后,递归会非常好写

分别解释一下:

1.若当前查询第0级字符串的某一字符,直接return即可

2.第 n 级的字符串由两部分组成:n - 1级的字符串 + n - 1级反转后的字符串

如果 要 查询的字符 i 在前一部分,那么其实就是查询 上一级字符串 的第 i 项

3&&4.如果要 查询的字符 在后一部分,即 i>=len[n-1]+1,此时分两种情况:
。。。。。。。1)若要查询的是后一部分的第一项,那么它就是翻转前字符的最后一项
。。。。。。。2)否则,它就是翻转前字符的上一项(理解一下)

“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”

总结:我们可以先把 len[i] 给求出来,因为最大查询是 1 0 18 ≈ 2 60 10^{18}≈ 2^{60} 1018260,因此len数组长度最大只有60

对于每一个查询q,我们可以先二分查找出len[i]大于q的最小 i 值,然后按上面的递归,查找到在初始字符串中的位置

我的 “伪” 代码:

	cin>>str;
	cin>>t;
	for(int i=1;i<=t;i++){
		cin>>q[i];
	}
	len[0]=str.size();
	for(int i=1;i<=60;i++){
		len[i]=len[i-1]*2;	
	}
	for(int i=1;i<=t;i++){
		int l=1,r=60,mid;
		long long id,to=q[i];   //id表示len[id]大于本次查询q[i]的最小id值,to表示查询第几项
		while(l+1<r){
			mid=(l+r)/2;
			if(check(mid,i)==1){
				r=mid;
			}
			else l=mid;
		}
		if(check(l,i)==1) id=l;
		else id=r;
		cout<<Find(id,to)<<endl;
	}

char Find(int n,long long to)//n为等级,to为查询第to项
{
	if(n==0) return len[to-1];
	if(to<=len[n-1]){
		Find(n-1,to);
	}
	else {
		long long nto=to-len[n-1];
		if(nto==1){
			to=len[n-1];
		}
		else {
			to=nto-1;
		}
		Find(n-1,to);
	}
}

衷心提醒:能开long long的一定开long long!

------------------------------------------------------------------------------------------------------------------(再看,再看,就把你喝掉)

T5 蜗牛老师的跳跃

说说我的广搜吧:

想到广搜是因为这道题有两个点:

1)蜗牛老师每跳一次花的时间相同 无权值
2)问题要找最小时间 求最小值

而广搜的原理不就是从一点开始均匀扩散,最先扩散到答案的点一定是最小值吗?

因此我选择了它;

先考虑边界:如果仅仅是每次跳到所有能跳的柱子,跳到南岸再结束,那么如何判断无法跳到呢?出现的重复情况怎么办呢?

每根柱子有一个上升时间 a ,沉水时间 b ,那么 r = a+b 即为这个柱子变动的周期:

每经过 r 个单位时间,这根柱子就会出现完全相同的状态

那么两根柱子呢?

想想,那就是他们 r 值的最小公倍数啊!

再推广:这 n 根柱子变动的周期,其实就是他们的最小公倍数(用 lcm 表示)

每经过 lcm 个单位时间,这些柱子就会出现完全相同的状态

我们先来讨论一个important的事情:lcm最大是多少呢?

由“ 1 ≤ a i , b i ≤ 5 1≤ai,bi≤5 1ai,bi5”,可知:每根柱子的周期为 2~10 ,于是我们的问题转化成:1000个 2~10 之间的数字,最小公倍数最大是多少?

假设:当前已知 lcm 为2,3,4,5,6,7,8,9,10的最小公倍数,

此时再有一个 2~10 之间的数 x ,那么lcm一定为 x 的倍数 因此: l c m ( l c m , x ) = l c m lcm(lcm,x)=lcm lcm(lcm,x)=lcm

于是:再有一个 2~10 的数对 lcm 就一定没有影响

问题进一步转化成了:2,3,4,5,6,7,8,9,10的最小公倍数?

我们把他们质因子分解:

2=2;
3=3;
4=2 * 2;
5=5;
6=2 * 3;
7=7;
8=2 * 2 * 2;
9=3 * 3;
10=2 * 5;

由此可以得出最大的lcm为:2 * 2 * 2 * 3 * 3 * 5 * 7 = 2520 !

非常小的数,不是么?

(小声bb:嗯,这里补充下 lcm 算法:lcm( a,b )=(a*b)/gcd(a,b) , gcd(a,b)可以用辗转相除法算出;

求多个数的 lcm :lcm( a,b,c ) = lcm( lcm( a,b ),c );

开个 f 数组: f [id] [i%lcm] 表示在 i 个时间里,跳上了第 id 根柱子,这是一种状态

状态最多有: n ∗ l c m = 1000 ∗ 2520 = 2520000 n * lcm =1000*2520=2520000 nlcm=10002520=2520000

显然,每个状态判断一次,无论空间还是时间都没有问题

拓展节点:

假设当前跳到第 id 根柱子,时间为 t ,每次拓展节点:用 k 枚举 max(0,id-5) ~ min(n+1,id+5) ;

我们要在第 t+1 个单位时间跳到第 k 根柱子上,若第 k 根柱子在 t+1 个单位时间在水面上,且未重复(f[k][(t+1)%lcm]==0),入队

如此搜下去即可。

当 id==n+1 时,输出time;

当队列为空时,说明:所以可能存在的状态都搜完了,再去搜一定重复,直接无情“-1”

“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”

总结:先求出 n 根柱子周期的 lcm ,然后开始广搜:

从北岸(编号为0,一直在水面上的柱子)开始跳,

每次拓展节点,确保可行性未重复

最终输出答案。

我的代码:

		lcm=1;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>p[i].a>>p[i].b;
			p[i].r=p[i].a+p[i].b;//r 变动一次的周期 
			lcm=(p[i].r*lcm)/GCD(lcm,p[i].r);
		}

		bool f[n+3][lcm+3];//标记是否重复 
		memset(f,0,sizeof(f));//初值 
		f[0][0]=1;//第0个单位时间在第0根柱子(北岸)状态已存在过
		p[0].a=1;//北岸(第0根柱子)始终在水面上
		p[0].b=0;
		p[0].r=1;
		p[n+1].a=1;//南岸(第n+1根柱子)始终在水面上
		p[n+1].b=0;
		p[n+1].r=1;
		q.push((ss){0,0});
		bool ff=0;//判断能否到达南岸
		while(!q.empty())//广搜 
		{
			int id=q.front().now;
			int ti=q.front().t;
			q.pop();
			if(id==n+1){
				cout<<ti<<endl;
				ff=1;
				break;//搜到结果
			}
			for(int i=max(0,id-5);i<=min(n+1,id+5);i++){
				if(((ti+1)%p[i].r!=0&&(ti+1)%p[i].r<=p[i].a)||i==0||i==n+1){//能跳 (仔细想想)
					if(f[i][(ti+1)%lcm]==0){//没重复
						q.push((ss){i,ti+1});
						f[i][(ti+1)%lcm]=1;//标记该状态已入队
					}
				}
			}
		}
		if(ff==0) cout<<"No"<<endl;	//队列为空,且为跳到,无情“-1”

温馨提醒:多组数据时,一定要记得每次清空!!!(呜呜呜,泪的教训)

dp的思路就不说了。不过,本人觉得dp的时间上限到lcm似乎就够了😏😏

“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”

最后,给这套题排个序吧:

T 1.100 < T 3.100 < T 2.30 < T 4.50 < T 4.100 < T 5.100 < T 2.100 T1.100<T3.100<T2.30<T4.50<T4.100<T5.100<T2.100 T1.100<T3.100<T2.30<T4.50<T4.100<T5.100<T2.100(嗯,个人观点,大佬勿喷🙏🙏)

啊!7578个字!迄今为止本人写过的最长文章!

(小声bb:喜欢童鞋的给个好评支持下吧~)

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值