Codeforces Round 912 (Div. 2)补题

Halloumi Boxes

题目大意:有一个数组a[],每次可选定一段长为k的区间,将区间内的数反转,问最后能不能使a[]按照非降序排列。

思路:不要想复杂了,只要这个k>=2那么就可以两两交换,那不就是冒泡排序嘛,直接就成立了。但是如果k是1,而且还有逆序的,肯定不可以,k=1,换了等于没换。

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

StORage room

题目大意:我们给定一个n*n的表M,要求一个大小为n的数组a[],使得Mij=a[i]|a[j](i!=j),如果数组不存在,输出“NO”;否则输出“YES”,并输出a[].

思路:既然涉及到位运算,那么我们就从二进制的角度来看。|的意义就是两个数中的当前位,只要有一个是1,那么结果的这一位就取1,我们每个数都与剩下的数进行了该运算,我们看这个数x与其他数进行或运算后产生的01串,可以发现,有些位上对于所有的串来说都是1,那么显然,x的这一位也应该是1,所以x的计算只用将这些数取&(都是1才为1)即可。为了保证初值不影响后续计算,我们将初值赋为1073741823(二进制下30个1,Mij的范围是小于2^30)。最后要考虑到不成立的情况,那么就将得到的数组去计算一遍,看能否得到M即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int m[1010][1010];
int a[1010];
signed main()
{
	int t;
	scanf("%lld",&t);
	while(t--)
	{
		int n;
		scanf("%lld",&n);
		for(int i=1;i<=n;i++)
		{
			a[i]=1073741823;
			for(int j=1;j<=n;j++) 
				scanf("%lld",&m[i][j]);
		}
		if(n==1) printf("YES\n"),printf("1\n");//任意一个数都可以
		else if(n==2) //恒成立的
		{
			printf("YES\n");
			printf("0 %lld\n",m[1][2]);
		}
		else
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					if(i!=j) a[i] &= m[i][j];
			
			int flag=1;
			for(int i=1;i<=n;i++)
			{
				for(int j=i+1;j<=n;j++)
				{
					if((a[i]|a[j])!=m[i][j])
					{
						flag=0;
						break;
					}
				}
				if(!flag) break;
			}
			if(flag) 
			{
				printf("YES\n");
				for(int i=1;i<=n;i++) printf("%lld ",a[i]);
				printf("\n");
			}
			else
			{
				printf("NO\n");
			}
		}
	}
}

Theofanis' Nightmare

题目大意:给定一个数组a[],我们可以将它化成若干个子数组,子数组之间没有重叠。我们给子数组按顺序标上序号,sumi作为第i个子数组的和,ans=sum(sumi*i),求ans的最大值。

思路:这道题是贪心题,贪心的思路特别妙。我们来看从一个位置划开意味这什么,意味着后面所有的数都要被多加一次,那么如果我们知道了所有数的和(后缀和),那么这个和如果大于0,对于结果的贡献肯定是正的,如果小于0,会对结果产生负面影响,自然不能从这个位置划开,首先,大家都避免不了要被算一次,剩下的是否会再被算就要看我们的后缀和了。所以,这个贪心的思路真的很妙,去考虑划分产生的意义。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200010],s[200010];
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]);
		s[n]=a[n];
		for(int i=n-1;i>=1;i--) s[i]=s[i+1]+a[i];
		int ans=s[1];
		for(int i=2;i<=n;i++)
		{
			if(s[i]>0) ans += s[i];
		}
		printf("%lld\n",ans);
	}
}

Maximum And Queries (easy version)

题目大意:现有一个大小为n的数组a[],我们最多可以进行k次操作,每次操作可以将一个a[i]加1,最后需要求出a[]的&,问最大结果是多少。

思路:这道题既然是位运算,那么我们就从二进制的角度来看,当所有数的某一位都是1的时候,那么结果的这一位就是1,我们要做的就是在k次操作中尽可能地使高位为1,那么其实可以直接模拟,从高位开始看,遍历每一位,假设要使当前这个数地这一位变成1,需要操作多少次,把次数累加起来,如果次数超过k,那么自然不可以,如果次数小于k,那么可以去看看后面的位还有没有能够变成1的。剩下的还有一个点就是来确定,当前这个数的这一位如果变成1,最少需要加多少。

我们以第j位为例,判断第j位是否是1(从后往前依次是第0位,第1位,......),a[i]&(1<<j)如果是1,那么就不需要操作,如果是0,那么就要考虑操作多少次:

7:111
4:100
7%4==3:11
8:1000
8%4==0:00
9:1001:
9%4==1:01

所以a[i]%(1<<j)就可以得到后j-1位的值,我们只用看它跟1<<j差多少,那么就要补多少。

至此,讨论清楚,问题解决。另外还要注意一点,如果当前位可以进行操作,需要实际去操作,否则会导致后面的统计出问题。还有如果是1<<i,默认是对int型进行操作,需要用1ll才会对long long型的1进行操作。

#include<bits/stdc++.h>
using namespace std;
#define int long long
//k<=1e18,差不多得2^60,
int a[200010],b[200010];
signed main()
{
	int n,q;
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i];
	while(q--)
	{
		for(int i=1;i<=n;i++) a[i]=b[i];
		int k;
		scanf("%lld",&k);
		int op=0;
		int ans=0;
		for(int i=60;i>=0;i--)
		{
			int top=0;
			for(int j=1;j<=n;j++)
			{
				int t=1ll<<i;
				if(a[j]&(1ll<<i)) continue;
				top += (1ll<<i)-(a[j]%(1ll<<i));
				if(op+top>k) break;
			}
			if(op+top<=k) 
			{
				op += top;
				ans += (1ll<<i);
				for(int j=1;j<=n;j++) 
				{
					if(a[j]&(1ll<<i)) continue;
					a[j] &= (1ll<<i);
				}
				
			}
		}
		printf("%lld\n",ans);
	}
}

 ps:不管什么时候遇到位运算,一定要从二进制的角度去考虑,这样会大大优化题目思路。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值