牛客-模拟、枚举与贪心 2022.11.16

本文探讨了矩阵消除游戏的解决方案,采用二进制枚举法选取行和列,确保全面覆盖所有可能。接着分析了贪心法在时间排序问题中的应用,按特定顺序遍历以寻找有效序列。最后,介绍了在处理变化值问题时的贪心策略,通过调整遍历顺序以达到最优解。文章通过具体例子展示了如何优化异或操作问题,利用前缀和提高效率。

矩阵消除游戏

先枚举行,再枚举列。枚举行的方法为二进制枚举法,为了避免遗漏掉情况,所以假定如果为一,则选取该行,并修改该行的所有数字为零,否则不选取。二进制枚举法即枚举从所有二进制为零到所有二进制为一的情况,这样可以避免遗漏。同时如果当前选取的行数大于目标操作数,则直接重新开始下一次选取。选取好行之后,再按照每列和的从大到小的顺序依次选取。

#include <bits/stdc++.h>
using namespace std;
int num1[20][20];
int num2[20][20];
bool cmp(long long a, long long b) {
    return a > b;
}
int main() {
    int n, m, k;
    cin >> n >> m >> k;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> num1[i][j];
            num2[i][j] = num1[i][j];
        }
    }
    if(n <= k || m <= k) {
        long long res = 0;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                res = res + num1[i][j];
        cout << res << endl;
    } else {
        long long res = INT_MIN;
        for(int i = 0; i <= (1<<n) - 1; i++) {
            long long ans = 0;
            int select = 0, tmp = i, start = 0;
            while(tmp) {
                if(tmp % 2 == 1) {
                    select += 1;
                    for(int j = 0; j < m; j++) {
                        ans = ans + num1[start][j];
                        num1[start][j] = 0;
                    }
                }
                tmp /= 2;
                start += 1;
            }
            if(select > k) {
                for(int i = 0; i < n; i++)
                    for(int j = 0; j < m; j++)
                        num1[i][j] = num2[i][j];
                continue;
            } else if(select == k) {
                res = max(res, ans);
                for(int i = 0; i < n; i++)
                    for(int j = 0; j < m; j++)
                        num1[i][j] = num2[i][j];
                continue;
            } else {
                vector<long long> tmpsum;
                for(int i = 0; i < m; i++) {
                    long long tmpsum1 = 0;
                    for(int j = 0; j < n; j++) {
                        tmpsum1 += num1[j][i];
                    }
                    tmpsum.emplace_back(tmpsum1);
                }
                sort(tmpsum.begin(), tmpsum.end(), cmp);
                for(int i = 0; i < k - select; i++)
                    ans = ans + tmpsum[i];
                res = max(res, ans);
                for(int i = 0; i < n; i++)
                    for(int j = 0; j < m; j++)
                        num1[i][j] = num2[i][j];
            }
        }
        cout << res << endl;
    }
    return 0;
}

华华听月月唱歌

贪心法。先按照开头序号从小到大排序,如果开头序号相同,按照结束序号从大到小排序。之后对排序结果进行遍历,如果第一个结果的开始序号不为一,直接输出-1即可。在遍历过程中,每次更新结束时间的最大值,如果到达n,则直接输出结果结束即可。如果结束时间最大值在这一次循环中没有更新,说明无法遍历到n,直接输出-1即可。

#include <bits/stdc++.h>
using namespace std;
bool cmp(pair<long long,long long>& a, pair<long long,long long>& b) {
    if(a.first == b.first)
        return a.second > b.second;
    return a.first < b.first;
}
int main() {
    long long n, m;
    cin >> n >> m;
    vector<pair<long long,long long>> p;
    for(int i = 0; i < m; i++) {
        long long l, r;
        cin >> l >> r;
        pair<long long,long long> tmp;
        tmp.first = l, tmp.second = r;
        p.emplace_back(tmp);
    }
    sort(p.begin(), p.end(), cmp);
    long long start = p[0].first, end = p[0].second;
    long long res = 1, idx = 1;
    if(start != 1) {
        cout << -1 << endl;
        return 0;
    }
    while(idx < p.size()) {
        long long tmpe = end;
        if(end == n)
            break;
        while(idx < p.size()) {
            if(p[idx].first <= end + 1)
                tmpe = max(tmpe, p[idx].second);
            else 
                break;
            idx += 1;
        }
        if(tmpe == end) {
            cout << -1 << endl;
            return 0;
        }
        res += 1;
        end = tmpe;
        if(end == n)
            break;
    }
    cout << res << endl;
    return 0;
}

「土」巨石滚滚

贪心法。分成三类,稳定值增加,不变,减少。首先肯定是增加的,其次不变的,最后减少的。增加的和不变的需要按照稳定值消耗值从小到大的顺序遍历,最后减少的贪心策略需要进行改变。如果只是按照之前的贪心策略,很容易举出反例。考虑从后往前遍历减少的情况,可以发现最后一个要符号条件,需要当前值大于消耗值,而恢复值是不需要看的,因为消耗完就结束了,所以恢复值不是那么重要。那么这样考虑来看,最好是给最后一个的恢复值最小,依次法,再考虑倒数第二个,同理可得。因此可以倒出减少的贪心策略应该是,按照恢复值从大到小排序即可进行遍历。

#include <bits/stdc++.h>
using namespace std;
bool cmp1(pair<long long,long long>& a, pair<long long,long long>& b) {
    return a.first < b.first;
}
bool cmp2(pair<long long,long long>& a, pair<long long,long long>& b) {
    return a.second > b.second;
}
int main() {
    int t;
    cin >> t;
    while(t--) {
        int n;
        long long m;
        cin >> n >> m;
        vector<pair<long long,long long>> smaller;
        vector<pair<long long,long long>> bigger;
        vector<pair<long long,long long>> equals;
        smaller.clear();
        bigger.clear();
        equals.clear();
        for(int i = 0; i < n; i++) {
            long long a, b;
            cin >> a >> b;
            pair<long long,long long> tmp;
            tmp.first = a, tmp.second = b - a;    
            if(b > a)
                bigger.emplace_back(tmp);
            else if(b == a)
                equals.emplace_back(tmp);
            else {
                tmp.second = b;
                smaller.emplace_back(tmp);
            }
        }
        sort(bigger.begin(), bigger.end(), cmp1);
        bool flag = true;
        for(int i = 0; i < bigger.size(); i++) {
            if(m >= bigger[i].first)
                m += bigger[i].second;
            else {
                flag = false;
                break;
            }
        }
        if(!flag) {
            cout << "No\n";
            continue;
        }
        sort(equals.begin(), equals.end(), cmp1);
        for(int i = 0; i < equals.size(); i++) {
            if(m >= equals[i].first)
                m += equals[i].second;
            else {
                flag = false;
                break;
            }
        }
        if(!flag) {
            cout << "No\n";
            continue;
        }
        sort(smaller.begin(), smaller.end(), cmp2);
        for(int i = 0; i < smaller.size(); i++) {
            if(m >= smaller[i].first)
                m -= smaller[i].first, m += smaller[i].second;
            else {
                flag = false;
                break;
            }
        }
        if(m < 0)
            flag = false;
        if(!flag) {
            cout << "No\n";
            continue;
        } else
            cout << "Yes\n";
    }
    return 0;
}

毒瘤xor

看到异或操作其实第一反应就是将数据改写成二进制形式,之后再观察样例的输出结果,可以发现如果当前二进制位零的个数超过了数据个数的一半,就讲结果这一位二进制写为零,否则写为一(因为需要多组可行解的情况下,数值尽可能小)(零的个数正好为一半时可以为零,也可以为一,但是为了数值尽可能小,所以选择为零)但是直接遍历只能过60%,还需要优化。考虑到区间问题,可以使用前缀和进行优化,这样对于区间求解可以直接一步到位求解。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int presum[MAXN][32] = {0};
int main() {
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) {
        long long a;
        cin >> a;
        for(int j = 0; j <= 30; j++, a /= 2)
            presum[i][j] = presum[i - 1][j] + a % 2;
    }
    int q;
    cin >> q;
    for(int i = 0; i < q; i++) {
        int l, r;
        cin >> l >> r;
        long long res = pow(2, 31) - 1, sum = 1;
        for(int j = 0; j < 31; j++, sum *= 2) {
            int one = presum[r][j] - presum[l - 1][j];
            if(one * 2 >= (r - l + 1))
                res = res - sum;
        }
        cout << res << endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值