Codeforces Round 882 (Div. 2)(VP)

本文探讨了如何处理数组,以最小化k组的和,通过贪心策略优化XOR运算,以及在二进制字符串操作中利用FenwickTree进行区间更新和查询。文章中展示了不同的算法思路,包括从后往前计算差值、消除最大差值、以及通过二进制组合寻找最大构造数。同时,提到了在动态更新和查询过程中可能出现的问题及解决方案。
摘要由CSDN通过智能技术生成

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";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值