队友题解,看不懂可以看他的。
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| ∣ai−aj∣+∣aj−ak∣+∣ak−al∣+∣al−ai∣
其中 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 1≤i,j,k,l≤n 。
这里 ∣ 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 块硬币最多用 2 2 2 个,否则 3 3 3 个 1 1 1 块硬币就可以被 1 1 1 个 3 3 3 块硬币平替。
- 发现 3 3 3 块硬币最多用 1 1 1 个,否则 2 2 2 个 3 3 3 块硬币就可以被 1 1 1 个 6 6 6 块硬币平替。
- 发现 6 6 6 块硬币最多用 4 4 4 个,否则 5 5 5 个 6 6 6 块硬币就可以被 3 3 3 个 10 10 10 块硬币平替。
- 发现 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 块硬币来凑。然后:
- 如果有 3 3 3 个以上 3 3 3 块硬币,而且还剩下 1 1 1 块钱,就可以合成一个 10 10 10 块硬币。
- 如果每有 5 5 5 个 3 3 3 块硬币,就可以合成一个 15 15 15 块硬币。
- 如果每有 2 2 2 个 3 3 3 块硬币,就可以合成一个 6 6 6 块硬币。
- 剩下的钱都可以用 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 1≤x≤n )是从顶部开始计数的行号, y y y ( 1 ≤ y ≤ m 1 \leq y \leq m 1≤y≤m )是从左边开始计数的列号。保证在不同的单元的网格中正好有 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(∣x−x1∣+∣y−y1∣,∣x−x2∣+∣y−y2∣) 。
你的任务是在进行查询后确定其中一个地雷的位置。
思路:
这个题用自己的思路写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<(x⊕y)<x 。
-
通过设置 x = y x = y x=y 或设置 x = x ⊕ y x = x \oplus y x=x⊕y 更新 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 n⊕m 二进制位上为 1 1 1 的位置进行操作。
不过直接给 n n n 异或 n ⊕ m n\oplus m n⊕m 有可能导致操作数太大,我们需要把 n ⊕ m n\oplus m n⊕m 拆成几部分,一个一个给 n n n 异或,最后得到 m m m。 问题在于怎么拆。
发现
n
⊕
m
n\oplus m
n⊕m 的所有为
1
1
1 的二进制位,一定是
n
,
m
n,m
n,m 其中一个贡献了
1
1
1,另外一个贡献了
0
0
0。于是可以把
n
⊕
m
n\oplus m
n⊕m 的所有为
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&n
,a2=n^m&m
。
如果我们一开始给 n n n 异或上 a 2 a2 a2 的部分 y y y,那么 n ⊕ y n\oplus y n⊕y 铁大于 n n n,肯定不满足条件(其实 n ⊕ y n\oplus y n⊕y 相当于 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,n⊕x 一定小于等于 n n n,只要 x ≠ 0 , n x\not=0,n x=0,n,一定满足条件(其实 n ⊕ x n\oplus x n⊕x 相当于 n − x n-x n−x)。
而我们如果给 n n n 异或了 a 1 a1 a1 的部分,得到的结果也不能去异或 a 2 a2 a2 的部分,因为 n n n 完全不包含 a 2 a2 a2 的部分, n ⊕ x n\oplus x n⊕x 一定也完全不包含,异或 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
t∣y<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 t∣a2 即可。这时候有解条件就变成了 t ∣ a 2 < n , t > a 2 t|a2<n,t>a2 t∣a2<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;
}