Educational Codeforces Round 152 (Rated for Div. 2)(B+C)

  题目链接:仪表板 - 教育代码部队第 152 轮(评级为 2 区) - 代码部队 (codeforces.com)

目录

B. Monsters

        一、题目描述

测试样例:

二、解题思路

三、代码实现

四、总结

 C. Binary String Copying

        一、题目描述

测试样例:

二、解题思路 

三、代码实现

四、总结



B. Monsters

一、题目描述

        Monocarp正在玩另一款电脑游戏。再一次,他的角色杀死了一些怪物。游戏中有n个怪物,编号从1到n,其中第i个怪物最初拥有ai生命值。

        Monocarp的角色有一个技能,对当前生命值最高的怪物造成k点伤害。如果有几个,则选择索引较小的那个。如果怪物的生命值在Monocarp使用技能后小于等于0,那么它就会死亡。

        Monocarp使用他的能力,直到所有的怪物死亡。你的任务是决定怪物死亡的顺序。

输入
第一行包含一个整数t(1≤t≤10^4)——测试用例的数量。

每个测试用例的第一行包含两个整数n和k(1≤n≤3⋅10^5;1≤k≤10^9)-怪物的数量和怪物技能造成的伤害。

第二行包含n个整数a1,a2,…,an(1≤ai≤109)-怪物的初始生命值。

所有测试用例的总和不超过3⋅105。

输出
对于每个测试用例,打印n个整数——怪物的索引,按照它们死亡的顺序。

测试样例:

输入

3
3 2
1 2 3
2 3
1 1
4 3
2 8 3 5

输出

2 1 3 
1 2 
3 1 2 4 

二、解题思路

因为数据 t <= 1e4, n <= 3e5;所以读入数据的时间复杂度为 n*t <= 3e9 ;

因为有t组测试用例,所以在每组数据中,时间复杂度只能为O(n)才能通过,当数据不取极限值的时候,每组数据的时间复杂度可以为O(n*logn);

情况1: 假设只有两个小怪物,并且他们的血量a1,a2“都是”k的整数倍,开始的时候肯定会对其中一个血量高的进行伤害,当进行多次伤害后,他们的血量会变得一样,再经过多次后,他们的血量都会变成k,所以此时肯定会对1号小怪物进行伤害,因为它的序号低,所以1号小怪物先被杀死,死亡序列一定是1号小怪在2号小怪的前面(1, 2 )。

同理,当有多个小怪物的血量都为k的整数倍时,一定是序号小的那个小怪先被杀死;

情况2.假设只有两只小怪物,并且他们的血量a1,a2“都不是”k的整数倍,开始的时候肯定会对其中一个血量高的进行伤害,当进行多次伤害后,他们的血量肯定都会小于k并且大于0,假设多次伤害后的血量为a1=2,a2=3,假设k=4,此时肯定会对血量高的小怪进行伤害,所以2号小怪物先被杀死,死亡序列一定是2号小怪在1号小怪的前面(2, 1 )。

所以,当两个小怪的物的血量都不为k的整数倍时,一定对k取余后,余数大的那个小怪先被杀死;

情况3.假设只有两只小怪物,并且他们的血量a1k的整数倍,a2不是k的整数倍,开始的时候肯定会对其中一个血量高的进行伤害,当进行多次伤害后,a1会变成k,a2会变成一个小于k的数并且大于0的数,假设a2变成k-1,所以1号小怪物先被杀死,死亡序列一定是1号小怪在2号小怪的前面(1, 2 )。

所以,当两个小怪的物的血量一个为k的整数倍,另一个不是时,一定是k的整数倍的那个,先被杀死;可以理解为,两者都对k取余后,余数小的那个小怪先被杀死;

综上所述:小怪的血量只与自身的序号和对k取余后的值有关;所以,可以将小怪的血量同时对k进行取余。但是,都进行取余后,第二种情况和第三种明显相悖,所以要进行稍微的转换:血量ai对k取余时,如果血量为k的整数倍时,将血量ai变为k在情况3中,a1,a2对取余后,a1变成k,a2变成k-1,a1先被杀死。所以情况2和情况3都得出结论:对ai对k取余,余数不同时,余数小的先被杀死;情况1得出结论,ai对k取余,余数相同时,序号小的先被杀死。

所以,解题思路为,用一个结构体存储每个小怪物的血量和序号,在读入小怪物的血量后,将血量对k进行取余,血量为k的整数倍(对k取余为0)的,血量变为k。

之后,对整个结构体数组进行排序,如果两个小怪物的血量相同,序号小的排在前面,血量不同时,血量大的先被杀死。(代码中布尔型的cmp函数的含义)

可以直接调用sort函数,并且自己手写一个排序的cmp函数,时间复杂度大概为O(n*logn),因为此题的数据并不是极限值,所以勉强可以通过。

三、代码实现

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<iostream>
#include<algorithm>
using namespace std;
 
typedef struct edges
{
	int xv;
	int zhi;
}edges;
 
int n,k ;
 
bool cmp(edges A,edges B )
{
	if( A.zhi != B.zhi  ) return A.zhi > B.zhi;	
	return A.xv < B.xv;	
}
 
int main( )
{
	int t;
	scanf("%d",&t);
	
	while(t-- )
	{
		 scanf("%d%d",&n,&k );
		
		edges a[n*2];
		
		for(int i=1;i<=n;i++ )
		{
			scanf("%d",&a[i].zhi );
		
			a[i].zhi%=k;
			
			if(a[i].zhi ==0 ) a[i].zhi = k;
			
			a[i].xv=i;
		}
		
		sort(a+1,a+1+n,cmp);
	
		for(int i=1;i<=n;i++ )
			printf("%d ",a[i].xv);
			
		puts("");
	}
	return 0;
}

​

​

四、总结

做题之前先进行时间复杂度的分析,在确定自己思路的时间复杂度正确之后,再进行代码的实现。只前用的优先队列和set存结构体进行排序,但是算法的时间复杂度为O(  k  ),当k过大时,会超时,但是是当代码实现之后,才发现会超时,导致做题和补题浪费了大量的时间。

 C. Binary String Copying

一、题目描述

        给定一个由n个字符0和/或1组成的字符串s。

        把这个字符串复制m个,第i个拷贝是字符串ti。
        然后对每个副本只执行一个操作:对于第i个副本,对它的子字符串[li;ri]排序(从第i个字符到第i个字符的子字符串,包括两个端点)。注意,每个操作只影响一个副本,并且每个副本只受一个操作的影响。

        你的任务是计算t1, t2,…,tm中不同字符串的个数。注意,只有在操作后至少有一个副本保持不变时,才应该计算初始字符串s。

输入

第一行包含一个整数t(1≤t≤104)——测试用例的数量。

每个测试用例的第一行包含两个整数n和m(1≤n,m≤2⋅105)—s的长度和拷贝数。

第二行包含n个字符0和/或1 -字符串s。

然后是m行。其中第i个包含两个整数li和Ri(1≤li≤Ri≤n)——对第i次拷贝执行的操作的描述。

n除以所有测试用例的和不超过2·105。m除以所有测试用例的和不超过2·105。

输出
打印一个整数- t1,t2,…,tm中不同字符串的个数。

测试样例:

输入

3
6 5
101100
1 2
1 3
2 4
5 5
1 6
6 4
100111
2 2
1 4
1 3
1 2
1 1
0
1 1

输出

3
3
1

二、解题思路 

        首先进行时间复杂度的分析,因为外层的 t <= 1e4,内层的 m <= 2e5,m*t <= 2e9,所以对于每一组数据,时间复杂度最好为O(m),当数据量不取极限值的时候,时间复杂度为O(n*logn)的算法勉强能过。

        在排序的时候,无论前面的序列是什么,在最后面的1的始终不变;无论后面的序列是什么,在最前面的0也始终不变。所以每个区间的排序,事实上只对l 到 r 上第一个 1 到最后一个 0 的这部分区间进行排序所以,每次区间的有效区间就是 l 到 r 上第一个 1 到最后一个 0 的区间,,

        有效区间不同,即要进行排序的区间不同,那么排序后得到的序列也一定不同。        

        所以,我们确定当前这一段区间上的有效区间后,把这个区间的有效区间[l',r']存储起来就可以,因为同一段有效区间排序结果一定是相同的,因为set有自动取重功能,所以我们用 set存储有效区间的两端l和r。

        对于有效区间,我们只需要遍历一遍字符串就可以求出每一位字符的前一个 0 和 下一个 1,从而简单的确认出哪一部分是影响排序的。

        用pre数组记录前面0的位置,即pre[i]表示前面离s[i]最近的0的下标(可以为i);

        用next数组记录前面0的位置,即next[i]表示后面离s[i]最近的1的下标(可以为i);

        因为pre记录的是前面的位置,所以确定pre[i]的值时,要对数组s从前往后遍历。当s[i]=='0'时,pre[i]=i,意思是s[i]离前面最近的0就是它本身,当s[i]=='1'时,pre[i]=pre[i-1],意思是s[i]离前面最近的0的下标和s[i-1]离前面最近的0的下标相同,而如果s[i-1]=='1',那么pre[i-1]==pre[i-2];以此类推,当s[i]!='1'时,pre[i]的值一定是由前面的序列得到,总之,pre[i]的值一定是正确的。

        将pre[0]定义为0的原因,因为确定pre[i]的值,是从前往后找的,而当i==1,并且s[i]=='1'时,此时s[i]前面没有'0',将它前面的0的位置定义为下标0,如果pre[i]==0,说明他前面全是1,这是一个无效区间(不用进行排序的区间(r<=l ));

        将next[n+1]定义为0的原因,因为确定next[i]的值,是从前往后找的,而当i==n,并且s[i]=='0'时,此时s[i]后面没有'1',将它后面的1的位置定义为下标n+1,如果next[i]==n+1,说明他后面全是0,这是一个无效区间(不用进行排序的区间(r<=l ));

        

l=next[l],将当前的左边界改为l 到 r 上第一个 1 的位置,l 到 r 上第一个排序时要变的位置。

r=pre[r], 将当前的右边界改为r 到 l 上第一个 0 的位置,r 到 l 上第一个排序时要变的位置。

即将把左边界往右扩, 右边界往左扩。

下面提到的r,l是指有效区间的右边界,左边界;

当r <= l 时,说明这个有效区间不存在,也就是说这种情况不需要进行排序,这种情况要特殊处理,在set里面存入{0,0}这对数;所有的 r <= l 的情况,都存入{0,0}这对数,因为set有自动取重功能,不论存多少个{0,0},在set内部只显示一个{0,0};也就是说,不论 r 和 l 的值是多少,只要 r <= l ,有效区间就不存在,当前这个排序操作得到的数组就是原数组,无论多少个这样的操作、多少个这样的 l 和 r,得到的都是原数组,都是这一种类型的数组。

三、代码实现

#include<iostream>
#include<set>
using namespace std;
 
int main( )
{
	int t; scanf( "%d" , &t );
	while( t-- )
	{
		int  n ,  m; scanf( "%d %d" , &n , &m );
		
  		char s[ n * 2 ]; scanf( "%s" , s + 1 );
  	
  		int next[ n + 3 ] , pre[n+3] ; //标记后面的1的位置;标记前面的0的位置; 
  		
  		next[ n + 1 ] = n + 1;
    	pre[ 0 ] = 0;
    	
    	for ( int i = 1 ; i <= n; i ++ )
        	if ( s[ i ] == '0' ) pre[ i ] = i;
        	else pre[i] = pre[ i - 1 ]; 
        	
    	for ( int i = n ; i >= 0 ; i -- )
        	if ( s[ i ] == '1' ) next[ i ] = i;
        	else next[ i ] = next[ i + 1 ];
  		
  		
    	set< pair < int , int >  >st;
    	
		while ( m-- )
    	{
        	int l,r;
        	scanf( "%d %d" , &l , &r );
       		
       		
        	l = next[l], r = pre[r];
        	
        	if (l > r) l = r = 0;
           
        	st.insert( { l , r } );
    	}
    	
    	printf( "%d" , st.size( ) );
	 	puts("");
	}
	
	return 0;
}

四、总结

        有些时候要注意时间复杂度和空间复杂度,注意内存是否超限,这题我本来的思路是将区间(l,r)部分截取,存储到smid;并且将区间(l,r)前面的部分截取,存储到sl;将区间(l,r)后面的部分截取,存储到sr;随后用<algorithm>头文件中的sort函数对smid进行排序,在将sl+smid(排序后的)+sr,得到一个序列s1,用map<string,int>m,将s1存入map,最后输出map的长度;

        sort函数的时间复杂度为n*logn,所以每组数据的时间复杂度为O(m*n*logn)一定会超时,而且一直用map存储排序后的各个字符串,也导致空间超限。

        想到一个思路时,还是要先分析时间复杂度在进行代码的实现。

"educational codeforces round 103 (rated for div. 2)"是一个Codeforces平台上的教育性比赛,专为2级选手设计评级。以下是有关该比赛的回答。 "educational codeforces round 103 (rated for div. 2)"是一场Codeforces平台上的教育性比赛。Codeforces是一个为程序员提供竞赛和评级的在线平台。这场比赛是专为2级选手设计的,这意味着它适合那些在算法和数据结构方面已经积累了一定经验的选手参与。 与其他Codeforces比赛一样,这场比赛将由多个问题组成,选手需要根据给定的问题描述和测试用例,编写程序来解决这些问题。比赛的时限通常有两到三个小时,选手需要在规定的时间内提交他们的解答。他们的程序将在Codeforces的在线评测系统上运行,并根据程序的正确性和效率进行评分。 该比赛被称为"educational",意味着比赛的目的是教育性的,而不是针对专业的竞争性。这种教育性比赛为选手提供了一个学习和提高他们编程技能的机会。即使选手没有在比赛中获得很高的排名,他们也可以从其他选手的解决方案中学习,并通过参与讨论获得更多的知识。 参加"educational codeforces round 103 (rated for div. 2)"对于2级选手来说是很有意义的。他们可以通过解决难度适中的问题来测试和巩固他们的算法和编程技巧。另外,这种比赛对于提高解决问题能力,锻炼思维和提高团队合作能力也是非常有帮助的。 总的来说,"educational codeforces round 103 (rated for div. 2)"是一场为2级选手设计的教育性比赛,旨在提高他们的编程技能和算法能力。参与这样的比赛可以为选手提供学习和进步的机会,同时也促进了编程社区的交流与合作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林未兮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值