没有输出的输入是不完整的

0%

kickstart-2019-G-T2-TheEquation

还是上周和同学一起尝试的kickstart第G轮比赛,这个是第二题的思路和解法。
在经历各种报错,各种思路修正之后终于搞定了!快写下来!

题目描述


题目理解

就是找一个最大的K,使得K和每个元素进行亦或运算的和小于某个值。

题目解答

Test set1 暴力求解

下面的代码仅仅适应于小测试集。而之所以设置从127开始找,是因为看到了他给的测试集的范围。
对于Test set1中的每个元素而言,0<=Ai<=100,所以可以用6位表示。M也是可以用6位来表示,如果K的最高位不是第6位,而是第7位甚至更高,那么取亦或之后必然会大于128也就大于M,所以对于这个测试集来说,只要从127往下找到第一个符合条件的数字即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include <vector>
#include <math.h>
#include <bitset>
// typedef long long ll;

using namespace std;
void solve(int case_num){
int N,M;
cin>>N>>M;
int res = 0;
int temp = 0 ;
vector<int> arr;
bool find = false;
for(int i =0;i<N;i++){
int mi;
cin >> mi;
arr.emplace_back(mi);
}
for(int i = 127;i>=0;i--){
for(int j=0;j<N;j++){
temp += arr[j]^i;
}
if(temp <= M){
find = true;
res = i;
break;
}
temp = 0;
}
res = find?res:-1;
cout << "Case #" << case_num << ": " << res << endl;

}

int main(){
int t;
cin >> t;
for (int i = 1; i <= t; ++i) {
solve(i);
}
return 0;
}

贪心+位运算巧妙运用

按照上面的方法,我们可以发现这个大样例的0<=Ai<=pow(10,15),对应M也是这个范围。我们可以发现要用49位来表示,也就是说从pow(2,50)开始往下找到第一个符合条件的即可。但是这个数太大了。
然后我们发现原来式子的值可以这么改。

这样我们就通过位运算来找最大的K值了。
怎么找呢。我们找K的时候也是按照比特位从高位开始找,尽可能让高位取1,注意这里要保证当我这个位置取1之后加上后面的所有值的最小值不会超过M,那么才可以取。如果取不了1,那就试一下可不可以取0,同理如果这个位置取0,也要看加上后面所有位所形成的最小值会不会超过K,若是没有超过,则可以取,如果超过,那么说明这个数不存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;
const int maxn = 50;
LL pre[maxn], zeros[maxn], ones[maxn], minc[maxn], vis[maxn];

int main(){
int T;
scanf("%d", &T);
for (int t = 1; t <= T; ++t) {
LL N, M;

scanf("%lld %lld",&N, &M);

for(int i = 0;i<maxn;i++){
vis[i] = 0;
}

for(int i = 0; i <N; i++){
LL temp;
scanf("%lld", &temp);
for(int j = maxn - 1; j >= 0; j--){
if((temp>>j)&1){
vis[j]++;
}
}
}

for(int i = maxn-1; i >= 0; i--){
LL a = vis[i] * (1LL<<i);
LL b = (N-vis[i]) * (1LL<<i);
ones[i] = a;
zeros[i] = b;
minc[i] = min(a,b);
}

pre[0] = 0;
for(int i = 1;i < maxn; i++){
pre[i] = pre[i-1]+minc[i-1];
}

LL res = 0;
LL temp = 0;
for(LL i = maxn - 1; i >=0; i--){
if((zeros[i]+temp+pre[i])<=M){
temp += zeros[i];
res += (1LL<<i);
}else if(temp+ones[i]+pre[i]<=M){
temp += ones[i];
}else{
res = -1;
break;
}
}
printf("Case #%d: %lld \n", t, res);
}
return 0;
}

这个地方一定需要搞清楚的是,从最高位开始找的时候,并不是取1就可以,因为后面所有的位的0和1的情况会形成一个数字的范围,而不是0.一定要注意。

本题收获

  1. 要注意看测试集的测试用例的条件,尤其是大小,直接会决定我们的数据类型是int或者long long或者其他。
  2. 要注意题目隐含的条件,这个条件并不是单纯的从最高位到最低位的贪心,而是有后续隐含条件的贪心,所以一定要注意。
  3. 对于位运算,一定要学会使用移位运算符以及对应的位运算操作,有时候可能会有巧妙的作用。
  4. 因为c++的灵活性,可以通过地址进行操作,所以在使用数组的时候一定要注意不要越界,在进行相关条件判断的时候一定要小心。当报错说运行时错误的时候,多半就是for循环的条件写错了或者数组越界了。还有当你发现在本地跑的和在kickstart上跑同一份代码,但是结果不一样的时候,多半也是地址错了,以后要注意!
  5. 做出来这个题目的感觉很爽啊,继续加油~