Codeforces Round 900 (Div. 3)补题

How Much Does Daytona Cost?(Problem - A - Codeforces

题目大意:现有一个数组,我们要从中找一个子数组使子数组中的众数是k(众数的意思是,在这段区间中出现次数最多的数)。

思路:实际上只要这个数存在即可,因为我们选择区间的时候,可以选只包含这一个元素的区间。

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

Aleksa and Stack(Problem - B - Codeforces

题目大意:我们给定一个正整数n,现在需要构造一个大小为n的数组a[],要求a[]严格递增,同时3*a[i+2]%(a[i]+a[i+1])!=0,输出构造好的数组。

思路:这道题除了已知的两个专门提出来的条件以外,还有一个隐形的条件,a[i]<=1e9,所以我们的构造结束后,需要令n=2e5去验证一下会不会爆int.这道题有个特别简单的构造思路,我们令a[1]=1,a[2]=3(实际上等于2也行,因为我们会去处理冲突),然后每次递增1,如果发生冲突的话,再往上增加1,依次类推,最后验证发现不会爆int,即合法。

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

Vasilije in Cacak(Problem - C - Codeforces

题目大意:给定三个数n,k,x,现在需要在1-n中挑选k个不同数,使它们的和为x,问能否实现,如果可以的话,输出“YES”,否则输出“NO”

思路:很妙很妙,过的时候我都没想到。我本来一直在找凑出来的策略,但是根本没必要,我们可以将上下界算出来,因为n中k个不相同的数,是有一个范围的,范围内的所有数实际上都可以被凑出来,然后我们只用判断x是否在范围内即可。现在主要是需要证明一下为什么范围内的数都可以被凑出来:

下限:1,2,...,k

sum=(1+k)*k/2;

if(x>sum)

只需要将d=x-sum尽可能平均地分配给这k个数即可。如果d%k为0,那么就是所有的数都加一个相同的值,否则就是先同时增加一个相同的值,然后剩下的余数,从后往前,每个数都加1即可。只要x<合法上限,那么就是不会超的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n,k,x;
		scanf("%lld%lld%lld",&n,&k,&x);
		int l=(1+k)*k/2,r=(n+n-k+1)*k/2;
		if(l<=x&&x<=r) printf("YES\n");
		else printf("NO\n");	
	}
}

Reverse Madness(Problem - D - Codeforces

题目大意:现有一个长度为n的字符串s和k个区间,对于这些区间满足以下条件:

对于1<=i<=k

l[i]<=r[i]

l[i]=[i-1]+1

l[1]=1,r[k]=n

我们现给出q个询问,对于每个询问给出一个x,我们需要找到一个区间,使得l[i]<=x<=r[i],然后定义a=min(x,l[i]+r[i]-x),b=max(x,l[i]+r[i]-x),反转s中的[a,b]段。

输出最后得到的字符串。

思路:我们来分析一下,这些区间是连续的,而且也是互不相交的,故而每段区间内部的变化是互不干扰的,而且,因为区间连续,所以区间可以拼接起来,那么我们对于答案,就可以按照已经分好的区间,一段一段来确定答案。然后我们再将目光放到每段区间内部,如下图,我们可以发现,实际修改的是一段关于中心位置对称的区间。那么交换的时候就是对称位置进行交换,既然区间分析麻烦,我们就来看单点,对于每个点它可以被包含在若干个需要修改的区间中,但修改操作是恒定的,修改一次就将它和它的对称位置交换,修改两次就再换回来。所以,如果我们可以知道当前位置被包含在几个区间中,那么就可以知道它和它对称位置的字符需不需要交换。另外只有左端点在它左边的区间是对它生效的,顺序访问即可,而且只需要访问一半。

#include<bits/stdc++.h>
using namespace std;
int l[200010],r[200010],vis[200010];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		string s;
		cin>>s;
		for(int i=0;i<m;i++) scanf("%d",&l[i]),l[i]--;
		for(int i=0;i<m;i++) scanf("%d",&r[i]),r[i]--;
		int q;
		scanf("%d",&q);
		memset(vis,0,sizeof vis);
		while(q--)
		{
			int x;
			scanf("%d",&x);
			x--;
			vis[x]++;
		}
		string res="";
		for(int i=0;i<m;i++)
		{
			int a=l[i],b=r[i];
			string s1="";
			for(int j=a;j<=b;j++) s1 += s[j];
			int cnt=0;
			for(int j=a;j<=(a+b)/2;j++)
			{
				cnt += vis[j]+vis[a+b-j];
				if(cnt%2) swap(s1[j-a],s1[a+b-j-a]);
			} 
			res += s1;
		}
		cout<<res<<endl;
	}
}

ps:这道题最关键的地方在于首先要意识到每段区间是互不干扰的同时这些区间可以拼成完整的字符串。然后要意识到,虽然一个区间内部可能有很多个小区间需要取反,这些小区间是对称的,所以取反本质上是落在一个点和它的对称点上。就是看它们需要交换多少次,如果是需要交换偶数次,实际上相当于没有进行交换,而且对于每一对点,只有左端点在它左边的区间才能对它产生影响。

Iva & Pav(Problem - E - Codeforces

题目大意:有一个长度为n的数组a[],定义f(l,r)=a[l]&a[l+1]&...&a[r],现给出q个询问,每个询问包含l和k两个值,我们需要找到最大的r,满足f(l,r)>=k。

思路:这里找最大的r,很容易想到二分来计算,那么对于二分出来的区间,如何快速获得这一段中的与的和呢?既然涉及到位运算,那么我们就从二进制的角度来分析,这段区间内的所有数,如果某一位上都为1,那么结果的这一位肯定也为1,否则一旦有一个为0,那么结果的这一位就为0,所以我们可以从统计这段区间上的某一位上有多少个1来考虑,如果1的个数小于区间长度,那么结果的这一位肯定为0,进而就能通过O(30)的时间复杂度快速算出这一段区间的与运算和。至于预处理,我们想要获得的是一段区间上的1的个数,那么很容易联想到前缀和,我们可以统计前j个数的第i位上1的个数,然后就可以很容易得到某个区间某一位上1的个数。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int x,k;
int a[200010],s[200010],p[30][200010];
int check(int y)//check怎么实现,要将时间复杂度限定在接近O(1),那么必须在开始询问之前就进行预处理
{
	int sum=0;
	for(int i=30;i>=0;i--)
	{
		int d=p[i][y]-p[i][x-1];
		if(d>=y-x+1) sum += (1<<i);
	}
	if(sum>=k) return 1;
	else return 0;
}
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=30;i>=0;i--)
		{
			for(int j=1;j<=n;j++)
			{
				if((1<<i)&a[j])p[i][j] = p[i][j-1]+1;
				else p[i][j] = p[i][j-1];
			}
		}
		//我们想知道的是以某个位置开始往后,最远到哪里,我们找到第一个递减的位置其实就可以了,
		int q;
		scanf("%lld",&q);
		while(q--)//这里就到1e9了,所以后面的查询能不能是logn或者O(1),O(1)不可能,那么就需要是log,那么就必须二分,check为O(1)
		//不能遍历,遍历就是O(n)了
		{
			scanf("%lld%lld",&x,&k);
			if(a[x]<k) printf("-1\n");
			else
			{
				int l=x,r=n;
				while(l<r)
				{
					int mid=(l+r+1)/2;
					if(check(mid)) l=mid;
					else r=mid-1;
				}
				printf("%d ",l);
			}
		}
		printf("\n");
	}
}

ps:涉及到位运算的一定要从二进制的角度去考虑我们实际想要得到的是什么,如何能得到。比如这道题,我们想要的是这段区间求&的后的值,那么我们考虑从二进制的角度如何得到,就是看区间内的数的每一位上的值是否都为1,如果都为1,那么结果的这一位就为1,诚然,对于每个区间都用二进制循环一遍肯定还不如直接暴力,但是,区间的意义就在于,我们可以进行预处理,进而快速获得1的个数,那么如何预处理呢,涉及到区间的预处理就要联想到前缀和。这是对区间很妙的预处理方式。

ps:对于一个数组中任意一段区间,对其进行位运算,上面这种预处理的方法应该都是最快得到结果的方法,预处理每一位前缀中1的个数。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值