A. The Man who became a God
题意:给定数组,将数组分成k组,使k组加起来的和最小,其中每组的元素值为abs(a[i] - a[i + 1]) 1 <= i < n (1-indexed)
思路:从后往前求absolute value的绝对值数列,然后把最后一个元素弹出(因为按题目条件最后一个元素不参与到和的计算中)。 对剩下的元素逆序排序,可以按得到前k-1大个元素,将k-1个元素分为一组,其他元素分为一组,刚好k组,可以使和最小。
先总结:上面是一开始的思路,但是发现有一个BUG: 比如这种测试实例1 10 11 100 101,数列差应该是:9 1 89 1 排序后是:89 9 1 1, 很明显11和100之间的差值最大,如果把100单独列为一个区间,那么1 10 11, 100, 101就是三个区间了。但是又发现,如果把100, 101作为一个区间,其他的作为另一个区间,就刚好两个区间,但是再计算数值的时候,除去89以外其他数值都要计算。
也就是说,把最大的差值排在前面消去的时候,其实是为了消除11和100的差值,也就是在11和100之间放分界线,至于100和101之间放不放分界线,要看100和101之间的差值...虽然一开始的思路在细节上有点歪曲,误以为消去最大差值是将后面那个大数单独作为一个区间,但是方法正确,ac了...
void solvea(){
int tc; cin >> tc;
while (tc--){
int n, k; cin >> n >> k;
vi a(n); liter(x, a) cin >> x;
vi prefix(a);
for (int i = 0; i < n - 1; ++i){
prefix[i] = abs(prefix[i] - prefix[i + 1]);
}
prefix.ppb;
sort(rall(prefix));
//liter(x, prefix) cout << x << endl;
int result = 0;
for (int i = k - 1; i < sz(prefix); ++i){
result += prefix[i];
}
cout << result << "\n";
}
}
B. Hamon Odyssey
题意:给定n个数,这n个数可以做&运算,问:在保证运算后结果最小的情况下,最多可以将n个数分为几组
思路:看一下数据范围1e5,二分暴力或者greedy。如果二分,那么判断分为k组的判断条件感觉不好确定。那么大概率是贪心思路。也就是说,对于每一个数x,在当前可以选择分组,或者不分组继续跟前缀的&运算结果做&运算。 接下来具体分析:设当前数为x,前缀运算结果为s,如果s>x,那么一定要做与运算,因为如果不做,那么最后最小的和肯定会比x大;如果s<x,那么也要做运算,如果不做运算,最后的和肯定会比s大。如果s==x,也要做与运算,因为如果不做,那么最后的和必定大于最小的和,因为如果x就是最小的运算结果,那么s+x>x,如果x还可以变的更小(比如x的下一个数是0),那么s也可以变为0,但是s没有变0,所以最后的和一定会>=s导致不是最小的和。 通过三种情况可以总结出贪心算法的实现思路:如果前缀和已经为0,那么就从当前位置分组,如果前缀和不为0,就继续运算。
void solveb(){
int tc; cin >> tc;
while (tc--){
int n; cin >> n; vi a(n);
liter(x, a){
cin >> x;
}
int sum = a[0];
int result = 0;
for (int i = 1; i < n; ++i){
if(sum != 0) sum &= a[i];
else{
result += 1;
sum = a[i];
}
}
if (result == 0) cout << 1 << "\n";
else {
cout << (sum == 0 ? result + 1 : result) << "\n";
}
}
}
C. Vampiric Powers, anyone?
题意:给定n个数,现在要根据这n个数往后面构造一些数,问:在后面构造数的话,最大能造到多少,输出它。可以执行的操作有XOR操作,并且如果有n个数,那么第n+1个数可以由任意下标i(1<=i, <= n) 到n的XOR结果得到。
比如1 2 3, 最大可以得到3
思路:想要根据XOR运算构造最大的数,那么这个数一定在前缀XOR和中出现过,比如1,2,4,1前缀和分别是1,3,7,6,可以发现我们可以选取的最大值已经出现过,只不过后来又在某一位上出现了1,导致那一位1被消为0了。针对这种情况,可以先选取i = 4构造一个1出来,这样两个1做亦或相互抵消,就可以得到最大值,计算过程为:1,3,7,6,7。所以问题转化成:从任意的下标i开始,找到从[i,n]的XOR运算最大值。这里为什么下标要从i开始,而不是从1开始,放个示例说明一下:2,7,1,不详细说明了。然而输入范围是1e5,BF必定TLE。于是要想办法在XOR运算的规律上下手。审题后发现,当前数x的数据范围是8bit整型,于是可以想到一个方法:暴力枚举1~256的所有二进制组合,再使用一个集合维护之前出现过的运算结果。当前缀xor枚举到当前这一位的时候,对二进制组合进行XOr运算得到结果t,然后判断该二进制组合在之前的结果中有没有出现过,如果出现过并且t比运算之前结果更大了,那么以当前x结尾的xor运算最大值应该是从二进制组合出现的后一位开始xor运算得到的。。
有点抽象,看看代码吧,就不觉得文字抽象了。
// rsq problem
void solvec(){
int tc; cin >> tc;
while (tc--){
int n; cin >> n;
vi a(n); liter(x, a) cin >> x;
int result = 0, cur_result = 0;
vi m;
for (int i = 1; i < (1 << 8); ++i){
m.pb(i);
}
set<int> sett;
liter(x, a) {
cur_result ^= x;
int t = cur_result;
liter(y, m){
if ((cur_result ^ y) > t&& sett.count(y)){
// cout << t << " " << y << " " << (cur_result ^ y) << endl;
t = cur_result ^ y;
}
}
result = max(result, t);
sett.insert(cur_result);
}
cout << result << "\n";
}
}
总结:题目一开始给人的感觉是rmq,但是暴力枚举区间On²应该会TLE,然后就开始想其他的办法。tag有点多,不知道咋分,都贴上吧:
bitmasks //xor
brute force //枚举二进制
data structures //set
dp //?
greedy //枚举二进制时,总是选择最大值
D. Professor Higashikata
题意:给个二进制字符串s,然后有m个区间,q个操作。m个区间组成新字符串t(区间是s中的区间),q个操作是更改当前s下标中的值取反。
输出q行,每行是在当前操作后,让t变为最大值的最小操作数,操作是可以选s中任意两个下标进行交换。
思路:输入s,然后用fenwick Tree做一个RUPQ(range update point query),记录s每个下标在t中出现的次数,这样可以Ologn的得到t中1和0的字符数量,并且在后面的单点更改中也可以很方便的更改1和0出现的次数。 树做好后,寄了
放个WA的代码,当时的思路是用树来维护更新0和1出现的次数,然后再建一个map,统计t中每个下标对应s下标的位置,然后每个询问q都遍历一遍下标得到需要移动的次数,然后计算移动次数时,计算了t中字符的移动次数,而不是s中的移动次数,寄。先码一下,有空再更改。
void solved(){
int n, m, q; cin >> n >> m >> q;
string s; cin >> s;
s.insert(s.bg, ' ');
FenwickTree ft_freq(n + 1);
ll sum1 = 0, sum0 = 0;
vi index;
for (int i = 0; i < m; ++i){
int l, r; cin >> l >> r;
ft_freq.update(l, 1);
ft_freq.update(r + 1, -1);
//issue to be TLE
for (int j = l; j <= r; ++j){
index.pb(j);
}
}
for (int i = 1; i <= n; ++i){
int t = ft_freq.rsq(i);
if (s[i] == '1') {cout << "sum1 cal : " << i << " " << (ft_freq.rsq(i)) << " " << endl;sum1 += t;}
else sum0 += t;
}
map<int, int> mapp;
for (int i = 1; i <sz(index); ++i){cout << s[index[i]]; mapp[index[i]] += 1;}
cout << endl;
liter(x, mapp) cout << x.first << x.second << endl;
cout << sum1 << " " << sum0 << endl;
for (int i = 0; i < q; ++i){
int p; cin >> p;
if (s[p] == '1'){
s[p] = '0';
sum1 -= ft_freq.rsq(p);
sum0 += ft_freq.rsq(p);
}
else {
s[p] = '1';
sum1 += ft_freq.rsq(p);
sum0 -= ft_freq.rsq(p);
}
int result = 0;
for (int i = 1; i <= sum1; ++i){
if (s[index[i]] == '1') result += 1; //key issue for TLE and WA
}
cout << abs(sum1 - result) << "\n";
}
}