Codeforces Round 931 (Div. 2)(A,B,C,D1)

比赛链接

队友题解,看不懂可以看他的。


A. Too Min Too Max

题意:

给定一个包含 n n n 个元素的数组 a a a ,找出表达式的最大值:

∣ a i − a j ∣ + ∣ a j − a k ∣ + ∣ a k − a l ∣ + ∣ a l − a i ∣ |a_i - a_j| + |a_j - a_k| + |a_k - a_l| + |a_l - a_i| aiaj+ajak+akal+alai

其中 i i i j j j k k k l l l 是数组 a a a 的四个不同的索引,其中 1 ≤ i , j , k , l ≤ n 1 \le i, j, k, l \le n 1i,j,k,ln

这里 ∣ x ∣ |x| x 表示 x x x 的绝对值。

思路:

感觉是个比较经典的贪心?和这个题有点像。

做法就是贪心地选择最大值,最小值,次大值,次小值。最大值减去最小值,最小值减次大值,次大值减次小值,次小值减最大值 之和就是最小的。

不太严谨的证明的话可以自己画个数轴划拉划拉,一开始的最大值和最小值的距离(绝对值的几何意义)这条线段是最长的,选上,剩下的最大就是最小值到次大值这条,选上,再剩下的最大就是次大到次小这条,最后剩下次小到最大这条。这四条线段是所有线段中最长的。

想要严谨证明可以去上面那个题的题解区翻翻,有点复杂。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=105;

int T,n,a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];
		sort(a+1,a+n+1);
		cout<<(a[n]+a[n-1]-a[1]-a[2])*2<<endl;
	}
	return 0;
}

B. Yet Another Coin Problem

题意:

您有 5 5 5 个不同类型的硬币,每个硬币的值等于前 5 5 5 个三角形数字中的一个: 1 1 1 3 3 3 6 6 6 10 10 10 15 15 15 。这些硬币种类繁多。您的目标是找到所需的这些硬币的最小数量,使其总价值正好为 n n n

我们可以证明答案总是存在的。

思路1:

一开始的思路是动态规划,但是动态规划跑不了 1 0 9 10^9 109,空间爆掉了。考虑到后面其实大多数时候都用的 15 15 15 块的硬币凑数,而 1 1 1 3 3 3 6 6 6 10 10 10 15 15 15 的最小公倍数为 30 30 30,所以我们只需要处理最前面 30 30 30 块需要怎么拿硬币,后面不停地补 15 15 15 块的硬币就好。

code1:

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int maxn=35;
const int inf=1e9;

int f[]={1,3,6,10,15};

int T,n;

int main(){
	vector<int> dp(maxn,inf);
	dp[0]=0;
	for(int i=0;i<30;i++){
		for(int j=0;j<5;j++){
			if(i+f[j]<=30)
				dp[i+f[j]]=min(dp[i]+1,dp[i+f[j]]);
		}
	}
	
	cin>>T;
	while(T--){
		cin>>n;
		int t=(n-30+14)/15;
		cout<<t+dp[n-t*15]<<endl;
	}
	
	return 0;
}

思路2:

发现 1 1 1 块硬币最多用 2 2 2 个,否则就可以被 1 1 1 3 3 3 块硬币平替。同理:

  1. 发现 1 1 1 块硬币最多用 2 2 2 个,否则 3 3 3 1 1 1 块硬币就可以被 1 1 1 3 3 3 块硬币平替。
  2. 发现 3 3 3 块硬币最多用 1 1 1 个,否则 2 2 2 3 3 3 块硬币就可以被 1 1 1 6 6 6 块硬币平替。
  3. 发现 6 6 6 块硬币最多用 4 4 4 个,否则 5 5 5 6 6 6 块硬币就可以被 3 3 3 10 10 10 块硬币平替。
  4. 发现 10 10 10 块硬币最多用 2 2 2 个,否则 3 3 3 10 10 10 块硬币就可以被 2 2 2 15 15 15 块硬币平替。

所以暴力枚举 1 , 3 , 6 , 10 1,3,6,10 1,3,6,10 的使用次数,剩下的补 15 15 15 块硬币就行了。

code2:

#include <iostream>
#include <cstdio>
using namespace std;

int T,n;

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		
		int ans=1e9;
		for(int a=0;a<=2;a++)
			for(int b=0;b<=1;b++)
				for(int c=0;c<=4;c++)
					for(int d=0;d<=2;d++)
						if(n>=(a*1+b*3+c*6+d*10) && (n-(a*1+b*3+c*6+d*10))%15==0)
							ans=min(ans,a+b+c+d+(n-(a*1+b*3+c*6+d*10))/15);
		
		cout<<ans<<endl;
	}
	return 0;
}

思路3:

其实和思路2有异曲同工之妙,发现其实 3 , 6 , 15 3,6,15 3,6,15 都是 3 3 3 的倍数,而 1 , 10 1,10 1,10 3 3 3 的倍数很近,所以我们可以把 n n n 全都先用 3 3 3 块硬币来凑。然后:

  1. 如果有 3 3 3 个以上 3 3 3 块硬币,而且还剩下 1 1 1 块钱,就可以合成一个 10 10 10 块硬币。
  2. 如果每有 5 5 5 3 3 3 块硬币,就可以合成一个 15 15 15 块硬币。
  3. 如果每有 2 2 2 3 3 3 块硬币,就可以合成一个 6 6 6 块硬币。
  4. 剩下的钱都可以用 1 1 1 块硬币来凑。

于是乎我们从上到下贪心地进行合并操作,最后得到的就是硬币个数最少的方案。

code3:

队友的神奇代码

请添加图片描述


C. Find a Mine

题意:

这是一个交互问题。

您将得到一个具有 n n n 行和 m m m 列的网格。

坐标 ( x , y ) (x, y) (x,y) 表示网格上的单元格,其中 x x x 1 ≤ x ≤ n 1 \leq x \leq n 1xn )是从顶部开始计数的行号, y y y 1 ≤ y ≤ m 1 \leq y \leq m 1ym )是从左边开始计数的列号。保证在不同的单元的网格中正好有 2 2 2 个地雷,表示为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 。您可以对交互器进行不超过 4 4 4 次查询,在这些查询之后,您需要提供其中一个矿井的位置。在每个查询中,您可以选择任何网格单元格 ( x , y ) (x, y) (x,y)

您将收到从两个矿井到所选小区的最小曼哈顿距离,即例如,您将收到值 min ⁡ ( ∣ x − x 1 ∣ + ∣ y − y 1 ∣ , ∣ x − x 2 ∣ + ∣ y − y 2 ∣ ) \min(|x-x_1|+|y-y_1|, |x-x_2|+|y-y_2|) min(xx1+yy1,xx2+yy2)

你的任务是在进行查询后确定其中一个地雷的位置。

思路:

这个题用自己的思路写WA麻了,理论能过但是就是被某些奇奇怪怪的数据卡死了,主要是还试不出来这组数据。

其实思路很简单,如果我们询问四个角上的点,那么根据返回值就会确定四条对角线:
在这里插入图片描述
然后这四条对角线上的两个对面的交点就是地雷可能的所在位置,如上图。

考虑到我们只需要确定一个地雷的位置即可,那我们只询问左上,右上,右下的点,然后再询问三条对角线的两个交点的其中一个,如果是地雷的位置就直接返回,否则就返回另一个交点即可。

在这里插入图片描述

code:

#include <iostream>
#include <cstdio>
using namespace std;
#define int long long

int T,n,m;

signed main(){
	cin>>T;
	for(int _=1;_<=T;_++){
		cin>>n>>m;
		int l1,l2,l3;
		cout<<"? 1 1"<<endl;
		cin>>l1;
		cout<<"? 1 "<<m<<endl;
		cin>>l2;
		cout<<"? "<<n<<" "<<m<<endl;
		cin>>l3;
		
		int x1=((l1+l2+3-m)%2==1)?0:(l1+l2+3-m)/2,y1=l1+2-x1;
		int x2=((n+1+l2-l3)%2==1)?0:(n+1+l2-l3)/2,y2=m-l2-1+x2;
		
		if(x1<1 || x1>n || y1<1 || y1>m){
			cout<<"! "<<x2<<" "<<y2<<endl;
		}
		else if(x2<1 || x2>n || y2<1 || y2>m){
			cout<<"! "<<x1<<" "<<y1<<endl;
		}
		else {
			int t;
			cout<<"? "<<x1<<" "<<y1<<endl;
			cin>>t;
			if(t==0)cout<<"! "<<x1<<" "<<y1<<endl;
			else cout<<"! "<<x2<<" "<<y2<<endl;
		}
	}
	return 0;
}

D1. XOR Break — Solo Version

题意:

给定一个初始值为 n n n 的整型变量 x x x

单次操作由以下步骤组成:

  • 选择一个值 y y y ,使 0 < y < x 0 \lt y \lt x 0<y<x 0 < ( x ⊕ y ) < x 0 \lt (x \oplus y) \lt x 0<(xy)<x

  • 通过设置 x = y x = y x=y 或设置 x = x ⊕ y x = x \oplus y x=xy 更新 x x x

确定是否可以使用最多 63 63 63 个操作将 x x x 转换为 m m m

如果是,请提供实现 x = m x = m x=m 所需的操作序列。

你不需要最小化操作的数量。

这里, ⊕ \oplus 表示 按位位异或 运算

思路:

首先我们异或的过程中是不会对 n , m n,m n,m 相同的二进制位进行操作,因为换了之后还要再换回来,相当于没换。而且这样也会导致中间的操作数变大,没有好处。因此我们操作的时候只对 n ⊕ m n\oplus m nm 二进制位上为 1 1 1 的位置进行操作。

不过直接给 n n n 异或 n ⊕ m n\oplus m nm 有可能导致操作数太大,我们需要把 n ⊕ m n\oplus m nm 拆成几部分,一个一个给 n n n 异或,最后得到 m m m。 问题在于怎么拆。

发现 n ⊕ m n\oplus m nm 的所有为 1 1 1 的二进制位,一定是 n , m n,m n,m 其中一个贡献了 1 1 1,另外一个贡献了 0 0 0。于是可以把 n ⊕ m n\oplus m nm 的所有为 1 1 1 的二进制位分成两部分:一部分是来自于 n n n 1 1 1,另一部分是来自于 m m m 1 1 1。不妨设前者为 a 1 a1 a1,后者为 a 2 a2 a2。可以知道,a1=n^m&na2=n^m&m

如果我们一开始给 n n n 异或上 a 2 a2 a2 的部分 y y y,那么 n ⊕ y n\oplus y ny 铁大于 n n n,肯定不满足条件(其实 n ⊕ y n\oplus y ny 相当于 n + y n+y n+y);而如果我们异或 a 1 a1 a1 的部分 x x x,因为 n n n 本来就包含 a 1 a1 a1,所以 x , n ⊕ x x,n\oplus x x,nx 一定小于等于 n n n,只要 x ≠ 0 , n x\not=0,n x=0,n,一定满足条件(其实 n ⊕ x n\oplus x nx 相当于 n − x n-x nx)。

而我们如果给 n n n 异或了 a 1 a1 a1 的部分,得到的结果也不能去异或 a 2 a2 a2 的部分,因为 n n n 完全不包含 a 2 a2 a2 的部分, n ⊕ x n\oplus x nx 一定也完全不包含,异或 y y y 后一定变大。那么我们只能在异或 a 1 a1 a1 的部分时,加点 a 2 a2 a2 的部分进去,这样就可以把 a 2 a2 a2 的部分异或掉。

为了要异或的操作数尽可能小,我们只取 a 1 a1 a1 部分中最大的那一位,假设叫 t t t。我们要保证带上 a 2 a2 a2 的部分 y y y 后,得到的 t|y (t|y)^n 两个数都小于 n n n,那么就需要 t ∣ y < n , t > y t|y<n,t>y ty<n,t>y(后者因为 n n n 包含 t t t t , y t,y t,y 完全没有重复的二进制位,所以相当于 (t|y)^n = n-t+y <= n )。

因为只要 y y y 的最高位低于 t t t,后面无论带多少低位,都会有 < t <t <t,否则光最高位就不满足条件,一定无解。所以我们直接一步到位, y = a 2 y=a2 y=a2,给 n n n 异或上 t ∣ a 2 t|a2 ta2 即可。这时候有解条件就变成了 t ∣ a 2 < n , t > a 2 t|a2<n,t>a2 ta2<n,t>a2。之后结果再异或上剩余的 a 1 a1 a1 即可。

其实上面说的有解条件 t > y = a 2 t>y=a2 t>y=a2 是一定成立的。因为 n n n m m m 第一个出现不同的二进制位置上一定是 n n n 1 1 1 m m m 0 0 0,否则 n ≯ m n\not>m n>m t t t 其实就是这个二进制位。 t t t 占据了最高的二进制位,那么 a 2 a2 a2 一定小于 t t t

不过这两步都有可能并不存在,当 a2=0 时,没有必要进行第一步,当 t^a1=0 (也就是 a 1 a1 a1 没有剩余的部分了)时,没有必要进行第二步。

code:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;

int T;
ll n,m,a1,a2,t;

int main(){
	cin>>T;
	while(T--){
		cin>>n>>m;
		a1=(n^m)&n;
		a2=(n^m)&m;
		for(ll i=a1;i;i^=i&-i)t=i;
		if((t|a2)<n){//a2^t^n等价于(t|a2)^n
			if(a2 && a1^t)cout<<2<<endl<<n<<" "<<(a2^t^n)<<" "<<m<<endl;
			else cout<<1<<endl<<n<<" "<<m<<endl;
		}
		else cout<<-1<<endl;
	}
	return 0;
} 
  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值