Codeforces Round 929 (Div. 3)题解

A. Turtle Puzzle: Rearrange and Negate(Problem - A - Codeforces

题目大意:现有一个数组a[],我们需要执行两个操作,首先将原数组按照任意顺序排序(当然也可以不排序),然后选择一段区间,将这段区间中的数全部变成它们的相反数(当然也可以选择长度是0的区间),问最后数组和的最大值是多少。

思路:很显然就是通过第一个操作将所有的负数放在一块儿,然后通过第二个操作将所有的负数变成它们的相反数,然后再相加。所以直接将所有数的绝对值加起来即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		int res=0;
		while(n--)
		{
			int x;
			scanf("%lld",&x);
			res += abs(x);
		}
		printf("%lld\n",res);
	}
}

B. Turtle Math: Fast Three Task(Problem - B - Codeforces

题目大意:有一个数组a[],我们可以执行若干次操作,每次操作可以选择一下两种之一:
1.删掉其中一个数
2.将其中一个数加1
问至少通过多少次操作可以使数组和可以整除3.

思路:这题有个很巧妙的地方,最多操作两次,因为原数组的和mod 3只有三种情况,0,1,2,如果是0,那么就不用操作;如果是1或者2,那么加上1或2就可以满足要求,加上1只需要操作一次,哪怕只用删除一个数也是一次,直接输出就可;加上2需要操作两次,那么就要看看能否通过删除某个数代替,遍历一遍即可。

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		int sum=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum += a[i];
		if(sum%3==0) printf("0\n");
		else if((sum+1)%3==0) printf("1\n");
		else
		{
			int flag=0;
			for(int i=1;i<=n;i++)
			{
				if((sum-a[i])%3==0) 
				{
					flag=1;
					break;
				}
			}
			if(flag) printf("1\n");
			else printf("2\n");
			
		}
	}
}

C. Turtle Fingers: Count the Values of k(Problem - C - Codeforces

题目大意:给定正整数a,b,l,要求找出三个非负数k,x,y满足l=k*(a^x)*(b^y),问不同的k有多少个。

思路:这里x和y都可以是0,那么k=l,所以k的上限是l,但是本题没有单调性,不能用二分,如果直接枚举的话时间复杂还是有点高,那么我们既然不能枚举k,可以用x和y入手,我们可以找出x和y的最大值,这个很好找,就看最多多少个a相乘不超过l,最多多少个b相乘不超过l即可。最后我们循环枚举x和y的值,然后判断是否有k即可,另外为了保证k不重复,我们可以用一个集合装数,然后输出集合大小。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int qm(int a,int m)
{
	int res=1;
	for(int i=1;i<=m;i++) res *= a;
	return res;
}
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int l,a,b;
		scanf("%lld%lld%lld",&a,&b,&l);
		int na=0,nb=0;
		int tmp=l;
		while(tmp/a)
		{
			na++;
			tmp /= a;
		}
		tmp=l;
		while(tmp/b)
		{
			nb++;
			tmp /= b;
		}
		set<int>q;
		for(int i=0;i<=na;i++)
		{
			for(int j=0;j<=nb;j++)
			{
				int d=qm(a,i)*qm(b,j);
				if(l%d==0) 
				{
					q.insert(l/d);
				}
			}
		}
		cout<<q.size()<<endl;
		
	}
}

D. Turtle Tenacity: Continual Mods(Problem - D - Codeforces

题目大意:现有一个数组a[],问能否找到一个排序使得a1%a2%a3%...%an!=0,如果可以就输出"YES",否则就输出"NO".

思路:比赛的时候猜了一个结论,找出所有数的最大公因数,如果这个最大公因数在数组中出现次数超过一次,那么就不可以,否则就是可以的。当时没仔细证明,直接交了然后就ac了,现在稍微证明一下。根据欧几里得算法(求gcd算法的原理),我们可以知道a%b一定是a,b最大公因数的的倍数,那么如果最大公因数在数组中只出现一次,很显然,我们把它放开头就可以避免出现余数为0的情况,但是如果出现两次及以上就避无可避的会出现g%g=0的情况。如果不出现,那么最后一定可以通过某种顺序得到这个最大公因数。至于这个结论是怎么猜出来的呢,其实也比较巧,首先很显然小的数mod大的数结果是不变的,而小的数有两种来源,一种是原数组中的数,另一种则是原数组中的数取模得到更小的数,所以我们如果尽早得到最小的那个数,然后从这个数开始取模的话,后面的除非遇到等于它的数,否则就不会出现0。原数组中最小的数很好找,问题在于通过取模得到的最小的数是多少呢,这里就想到最大公因数,数组中通过取模可以得到的最小的数就是整个数组的最大公因数,然后再统计一下最大公因数的出现次数。

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		scanf("%d",&n);
		int g=0;
		for(int i=1;i<=n;i++) scanf("%d",&a[i]),g=__gcd(g,a[i]);
		int c=0;
		for(int i=1;i<=n;i++)
		{
			if(a[i]==g) c++;
		}
		if(c<=1) printf("YES\n");
		else printf("NO\n");
	}
}

E. Turtle vs. Rabbit Race: Optimal Trainings(Problem - E - Codeforces

题目大意:现在有若干段路,我们需要计算跑完其中一部分连续的路后得到的收益,收益的计算如下:
定义一个整数u,然后跑过的第一个单位长度收益是u,跑过第二个单位长度的收益是u-1,以此类推,可以出现负数。
现在给定连续区间的左端点和初始值u,要求找出收益最大的情况下右端点的最小值。

思路:这题先用暴力的做法实现,很明显枚举计算最大值即可,一旦某个位置的值开始变小,那么就说明开始出现负数了,就没有再往后枚举的必要,这里直接暴力的时间复杂度较高,只能过5个数据,然后因为要快速算一段区间的和,所以想到用前缀和来优化,然后能过7个数据,时间复杂度还是高了。然后就想到用二分来找,但是当时可能比较着急,没有仔细分析性质,所以二分找出来的值并不合适。我们定义选定的区间的总长度为res,那么收益就是(2u+1-res)*res/2,起初我虽然二分的是r的值,但是判断条件选的却是收益,显然收益是一个二次函数,图像是抛物线,并非单调的。但是可能因为太着急了,并没有仔细分析性质,一直改一直交一直wa,最后终于想着分析一下,这一分析就找到问题的关键了,对于这个二次函数,很显然最大值应该在u+1/2的时候取到,但是因为值都是整数,所以要么在u处取到最大值,要么在u+1处取到最大值,所以我们可以找最接近u的数,那么我们可以通过找到第一个大于u的值来实现,因为跑道长度都是正的,所以一定是单增的,可以用二分来查找。那么最大值只有可能在找到的位置或者前一个位置出现,对于这两个值计算判断一下就可以实现。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,u,le;
int a[100010],s[100010];
int check(int mid)
{
	int res=s[mid]-s[le-1];
	return (u+u-res+1)*res/2;
}
 
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		scanf("%lld",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
		int q;
		scanf("%lld",&q);
		while(q--)
		{
			scanf("%lld%lld",&le,&u);
			int l=le,r=n;
			while(l<r)//找到第一个大于u的位置
			{
				int mid=(l+r)/2;
				if(s[mid]-s[le-1]>=u) r=mid;
				else l=mid+1;
			}
			if(s[r]-s[le-1]==u||r==le) printf("%lld ",r);
			else 
			{
				int a=s[r]-s[le-1],b=s[r-1]-s[le-1];
				if((2*u+1-a)*a<=(2*u+1-b)*b) printf("%lld ",r-1);
				else printf("%lld ",r);
			}
		}
		printf("\n");
	}
}

F. Turtle Mission: Robot and the Earthquake(Problem - F - Codeforces

题目大意:在一张n*m的方格地图中,有一些位置有石头,所有石头每秒向上移动一个单位,每一列都是循环的,及开头结尾可以视为连在一起,机器人从(0,0)出发,要达到(n-1,m-1),机器人每秒必须移动,而且移动方向只有三种选择:1.向右移动一个单位长度;2.向上移动一个单位长度;3向下移动一个单位长度。问最少需要多少秒可以到达,如果不能到达的话输出-1.

思路:这里本来想打表处理每个位置在某一时刻是否有石头,然后然后直接暴力搜索开始移动的。但是很显然空间和时间都会超,所以这个方法显然不可行。那么该如何做呢,有两种思路,一种思路是我们可以在移动的过程中记录时间,然后对于每一个将要移动到的位置,将它映射到最开始的图中进而判断此处是否能够移动。另一种思路很巧妙,假设石头是固定的,机器人和终点每秒向下移动,由于移动是相对的,所以这个可以等效替代前面一种方法的记录。在第二种方法中,机器人就只有三种操作了,静止不动(对应原来的上移,一上一下就相当于没动),向下移动两格(对应原来的向下移动),向右下移动(对应原来的向右移动)。然后我们可以通过宽搜得到机器人到最后一列的时间,然后对于每个位置,计算终点移动到此处需要花费多少时间,因为在我们的等效中,机器人是有静止不动这种操作的,而且石头我们也认为是静止不动的,所以此时移动的只有终点,那么就计算终点移动到此处花费的时间即可,然后在所有时间中找最小值,如果最小值不存在,那么就输出-1.

然后还有一个细节就是这个花费时间如何计算。这里用到的思路特别妙,我们在对最后一列进行讨论的时候是知道横坐标的,也就是说我们知道机器人在最后一列的什么位置,那么计算的思路就是考虑终点从最底层到机器人的位置需要移动多少,显然这个是很容易得到的,然后问题就转化成终点是往下移动到底端然后再到机器人位置,还是不用移动到底端,直接往下移动就可以到机器人的位置。因为我们计算实际上是用移动整趟的时间加上到机器人位置的时间。上面两种情况对应的移动整趟的时间是不一样的。先移动到底端的话,考虑整趟时间就是当前位置的时间还要加上一点;直接移动到目标位置的话就是当前时间减去一点,也即考虑它上一次到达底端的时间。我们可以用(t-h)/n上取整得到我们总共移动了多少个整趟,这么讲可能还是有点抽象,结合以下几种情况来讨论一下:

对于第一种情况,显然x-h的结果是负数,这种情况我们就单独考虑,直接返回h即可

对于第二种情况,x-h是一个大于0小于n的数,考虑实际的移动,显然是先移动到底端然后再加一个h,(x-h)/n上取整刚好是1,再加上h就是实际的移动。

第三种情况,x-h是一个大于0,小于n的数,考虑实际的移动,也是先退回到上一次移动到底端的时间再加上一个h得到的。而(x-h)/n上取整得到的刚好是1,符合我们的讨论。

第四种情况,考虑的时候应该是先移动到底端,然后再加上一个h,也就是完整的移动两趟,然后再加一个h得到,而x-h是一个大于n小于2n的数,上取整后刚好是2,也是符合实际情况的。

由于c++除法的性质,为了实现上取整,有两种思路,一种是调用ceil()函数,一种是加上n-1之后再进行整除,这样刚好实现上取整。

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=1010;
int g[N][N],d[N][N];
typedef pair<int,int> pii;
int n,m;
void bfs()
{
	queue<pii>q;
	d[0][0]=0;
	q.push({0,0});
	while(q.size())
	{
		auto t=q.front();
		q.pop();
		if(!g[(t.x+1)%n][t.y]&&!g[(t.x+2)%n][t.y]&&!d[(t.x+2)%n][t.y]) 
		{
			d[(t.x+2)%n][t.y]=d[t.x][t.y]+1;
			q.push({(t.x+2)%n,t.y});
		}
		if(!g[(t.x+1)%n][(t.y+1)%m] && !d[(t.x+1)%n][(t.y+1)%m]) 
		{
			d[(t.x+1)%n][(t.y+1)%m]=d[t.x][t.y]+1;
			q.push({(t.x+1)%n,(t.y+1)%m});
		}
	}
}

int cal(int x, int h) 
{
    if (x < h) return h;
    return ((x - h + n - 1) / n * n) + h;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		for(int i=0;i<n;i++)
			for(int j=0;j<m;j++) 
				scanf("%d",&g[i][j]),d[i][j]=0;
		bfs();	
		int ans = -1, tmp;
    	if(d[n-1][m-1]) 
		{
        	tmp = cal(d[n-1][m-1], 0);
        	ans = (ans == -1 || ans > tmp) ? tmp : ans;
    	}
    	for(int i = 0; i < n - 1; ++i) 
		{
        	if (!d[i][m-1]) continue;
        	tmp = cal(d[i][m-1], i + 1);
        	ans = (ans == -1 || ans > tmp) ? tmp : ans;
    	}
    	printf("%d\n", ans);	
	}
}
  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值