Codeforces Round #838 (Div. 2)

A. Divide and Conquer

题目链接:Problem - A - Codeforces

样例输入:

4
4
1 1 1 1
2
7 4
3
1 2 4
1
15

样例输出:

0
2
1
4

题意:一个数组是好的当且仅当所有的元素和是一个偶数,现在给我们一个初始数组,我们可以对这个数组进行操作,每次操作可以选择一个数并将其缩小为原来的一半,注意是下取整,现在问我们至少需要操作多少次才能把给定数组变为好的。

分析:容易发现,一个数组的元素和要么是奇数要么是偶数,如果一开始是偶数的话那么我们呢就没有必要进行操作,否则我们必须要改变一个数的奇偶性才能把数组变为好的,因为任意两个数之间是不会相互影响的,所以我们不需要同时对两个数进行操作,我们只需要遍历一遍所有的数,记录每一个数改变奇偶性所需要的最少操作次数,那么答案就是所有最小操作次数中的最小值。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=53;
int a[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int mi=0x3f3f3f3f;
		int ans=0;
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			ans+=a[i];
			int cnt=1;
			while((a[i]&1)==((a[i]/2)&1)) 
				cnt++,a[i]/=2;
			mi=min(mi,cnt);
		}
		if(ans&1) printf("%d\n",mi);
		else puts("0");
	}
	return 0;
}

B. Make Array Good

题目链接:Problem - B - Codeforces

样例输入: 

4
4
2 3 5 5
2
4 8
5
3 4 343 5 6
3
31 5 17

样例输出:

4
1 2
1 1
2 2
3 0
0
5
1 3
1 4
2 1
5 4
3 7
3
1 29
2 5
3 3

题意:一个数组是好的当且仅当这个数组中的任意两个数a和b都存在整除关系,即a|b或者b|a成立,现在我们需要对数组进行操作,每次操作可以把一个数a加上x,x是一个不大于a的非负整数,现在让我们进行不超过n次的操作使得数组变为好的,输出一种可行方案。

分析:我们可以考虑把所有数都变为2的幂次,这样的话一定有任意两个数之间存在整除关系,而且我们可以发现,对于任意一个数a,都会有一个x是2的幂次且满足a<=x<=2*a,那么我们只需要让a加上x-a即可将a变为大于等于a的第一个2的幂次,这样的操作次数一定是n,满足题意。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
int a[N],x[N];
vector<int>p;
int main()
{
	int T;
	cin>>T;
	long long t=1;
	while(t<=2e9) p.push_back(t),t<<=1;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			x[i]=*lower_bound(p.begin(),p.end(),a[i])-a[i];
		}
		printf("%d\n",n);
		for(int i=1;i<=n;i++)
			printf("%d %d\n",i,x[i]);
	}
	return 0;
}

C. Binary Strings are Fun

题目链接:Problem - C - Codeforces

样例输入: 

6
1
1
1
0
2
11
3
010
9
101101111
37
1011011111011010000011011111111011111

样例输出:

1
1
3
3
21
365

题意:一个奇数长度的01串b是好串需要满足以下条件:

对于任意一个奇数下标idx:

如果b[idx]=1,那么前idx个位置中1的个数要大于0的个数

如果b[idx]=0,那么前idx个位置中0的个数要大于1的个数。

对于一个长度为k的01串a,扩展后的01串长度就为2*k-1,假设扩展后的01串为c,那么就满足a[i]=c[2*i-1]

现在给定一个长度为n的01串a,求a的扩展串中是好串的数量,对998244353取模。

分析:设f[i]表示前i位扩展后占多数的情况数,g[i]表示前i位扩展后只多一个的方案数

那么我们现在来分析第i位的更新:

初始条件肯定有f[1]=g[1]=1

如果第i位和第i-1位是相同的,那么扩展后我们多出来需要自己填写的那一位可以填1也可以填0,这个时候就有f[i]=f[i]*2,要想保证前i位扩展后占多数的那一位比占少数的那一位只多1,那么需要在前i-1位扩展后占多数的那一位比占少数的那一位只多1的基础上在新扩展的那一位上填上与这一位相反的数字即可,即g[i]=g[i-1].

如果第i位和第i-1位是相反的,那么扩展后我们多出来需要自己填写的那一位一定要填与第i位相同的数字,而且要保证前i-1位扩展的扩展后占多数的那一位比占少数的那一位只多1,这样的话才能够实现前i位扩展后第i位上的数字出现次数多,而且只会多1,所以有f[i]=g[i],而且g[i]=g[i-1].

通过这样我们就能递推出最终的结果了。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10,mod=998244353;
int f[N];//f[i]表示前i位扩展后占多数的情况数
int g[N];//g[i]表示前i位扩展后只多一个的方案数 
char s[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d%s",&n,s+1);
		f[1]=g[1]=1;
		int ans=1;
		for(int i=2;i<=n;i++)
		{
			if(s[i]!=s[i-1]) g[i]=g[i-1],f[i]=g[i];
			else f[i]=f[i-1]*2%mod,g[i]=g[i-1];
			ans=(ans+f[i])%mod; 
		}
		printf("%d\n",ans);
	}
	return 0;
}

但是我们发现无论相邻两位是否相同都有g[i]=g[i-1],又因为一开始g[1]=1,那么可以知道所有的g数组都是1,所以我们可以省掉g数组了

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=2e5+10,mod=998244353;
int f[N];//f[i]表示前i位扩展后占多数的情况数
char s[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d%s",&n,s+1);
		f[1]=1;
		int ans=1;
		for(int i=2;i<=n;i++)
		{
			if(s[i]!=s[i-1]) f[i]=1;
			else f[i]=f[i-1]*2%mod;
			ans=(ans+f[i])%mod; 
		}
		printf("%d\n",ans);
	}
	return 0;
}

D. GCD Queries

题目链接:Problem - D - Codeforces

样例输入: 

2
2

1

1
5

2

4

1

样例输出:



? 1 2

! 1 2


? 1 2

? 2 3

! 3 3

题意:给定一个长度为n的排列0~n-1,现在让我们找出来0的下标,只需要找出来两个数i,j,只要满足其中一个数是0的下标即可。每次我们可以给出两个不同的下标i和j进行询问,电脑会反馈pi和pj的最大公约数,其中定义0与a的最大公约数就是a。让我们在询问次数不超过2*n的情况下找到满足题目条件的i和j。

分析:我们首先固定一个数,让这个数与其余的n-1个数进行询问,这样我们就可以唯一的确定第一个数的值。

比如说第一次我们固定的数是2,那么我们用2与其余所有数进行询问,那么询问的答案最大就是2,因为任何一个数与2的最大公约数不可能比2还大,所以第一个数就是2,同理可以得到其他数也是一样的。这样我们通过一次操作就可以得到第一个固定的数是2.我们已经知道了其余所有数与2的最大公约数是几了,对于最大公约数不是2的数我们就可以直接排除了,因为0与2的最大公约数是2,所以通过这一次操作我们就将n个数变为了n/2个数,花费了n次(注意这里确切是n-1次操作,不过为了方便书写方便就写n了,由于n>n-1,所以如果n可行那么n-1更可行)操作。下一次我们就至少可以将n/2个数变为n/4个数,花费的操作次数就是n/2,依次类推就有n+n/2+n/4+……<2*n的,所以这个方法是可行的,最后只剩下两个数时直接退出即可

如果我们第一个数是k,那么n次操作就可以将n个数变为n/k个数,那么这样最后花费的次数会更少,所以我们不必担心次数的问题。

但是我们忽略了一个问题,就是当第一个数是1的时候,我们花费了n次操作,结果全部是1,那么我们这n次操作就相当于只排除了第一个数。所以我们需要额外注意一下这种情况,我们让第一个数与第二个数进行一次询问,再让第一个数与第三个数进行一次询问,如果两次答案是一样的话那么说明第一个数不是0,我们直接把第一个数清空即可,如果两次答案不一样虽然不能说明第一个数一定是0,但是可以说明第一个数一定不是1,因为1与任何数询问得到的结果都是相同的,那么根据上面的分析我们可以发现询问次数一定是够用的。我们通过两次操作排除了一个数,就相当于原来问题是在2*n次询问中从n个数中找,现在变为了在(2*n-2)次询问中从n-1个数中找,这是等价的,只是问题规模变小了而已。

代码中是从最后一个数开始往前询问的。

因为每次询问完都会保留第一个数,所以每询问完一轮我都会对所有的数进行一次反转,这样上次固定的数一定会在这次被删除

注意这个细节。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		vector<int>p;
		for(int i=1;i<=n;i++) p.push_back(i);
		int x;
		while(p.size()>2)
		{
			reverse(p.begin(),p.end());
			vector<int> t;
			t.push_back(p[p.size()-1]);
			int mx=0;
			for(int i=0;i<=1;i++)
			{
				printf("? %d %d\n",p[p.size()-1],p[i]);
				fflush(stdout);
				scanf("%d",&x);
				if(x>mx)
				{
					mx=x;
					t.clear();
					t.push_back(p[p.size()-1]);
					t.push_back(p[i]);
				}
				else if(x==mx)
					t.push_back(p[i]);
			}
			if(t.size()==3)//说明最后一个数肯定不是0,主要是用于排除最后一个数是1的情况
			{
				p.pop_back();
				continue;
			}
			for(int i=2;i<p.size()-1;i++)
			{
				printf("? %d %d\n",p[p.size()-1],p[i]);
				fflush(stdout);
				scanf("%d",&x);
				if(x>mx)
				{
					mx=x;
					t.clear();
					t.push_back(p[p.size()-1]);
					t.push_back(p[i]);
				}
				else if(x==mx)
					t.push_back(p[i]);
			}
			p=t;
		}
		printf("! %d %d\n",p[0],p[1]);
		fflush(stdout);
		scanf("%d",&x);
	}
	return 0;
}

这里我再给出一种做法:

由于我们每轮操作之前都先判断第一个数是不是1,我们可以考虑以下这种解法:

先固定先两个数a和b,从第三个数c开始依次与这两个数进行询问,假设询问的结果为d和e

这个时候d和e有三种关系:

如果d=e那么一定有c!=0,直接排除继续该种操作即可。

如果d<e,那么a一定不会是0,因为一个数和0进行询问可以得到最大值,所以将c赋值给a。

如果d>e,那么b一定不会是0,因为一个数和0进行询问可以得到最大值,所以将c赋值给b。

按照这个操作一定可以在2*n次之内得到答案。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d",&n);
		vector<int>p;
		for(int i=1;i<=n;i++) p.push_back(i);
		int a=1,b=2,d,e;
		for(int i=3;i<=n;i++)
		{
			printf("? %d %d\n",a,i);
			fflush(stdout);
			scanf("%d",&d);
			printf("? %d %d\n",b,i);
			fflush(stdout);
			scanf("%d",&e);
			if(d==e) continue;
			else if(d<e) a=i;
			else b=i;
		}
		printf("! %d %d\n",a,b);
		fflush(stdout);
		scanf("%d",&d);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值