牛客-【237题】算法基础精选题单-第一章 模拟、枚举和贪心

模拟

NOIP2007 字符串的展开

第一道题目花费的时间是最多的,还wa了几次
需要特别注意的一个特殊情况时
1-a
这个时候a的ASCII是大于1的,需要满足的一个条件是’-'前后的符号属于同一个类型,要么都是数字,要么都是大写字母,要么都是小写字母
其他的情况可以通过测试样例发现

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
int n, m;
string ans;
char toLow(char x) {
    if (x >= '0' && x <= '9') {
        return x;
    } else if (x >= 'a' && x <= 'z') {
        return x;
    } else if (x >= 'A' && x <= 'Z') {
        return (x + 32);
    }
    return '0';
}
char toUp(char x) {
    if (x >= '0' && x <= '9') {
        return x;
    } else if (x >= 'a' && x <= 'z') {
        return (x - 32);
    } else if (x >= 'A' && x <= 'Z') {
        return x;
    }
    return '0';
}
// bool isValid(char x) {
//     if ((x >= '0' && x <= '9') || (x >= 'a' && x <= 'z') ||
//         (x >= 'A' && x <= 'Z'))
//         return 1;

//     return 0;
// }
int main() {
    int a, b, c;
    cin >> a >> b >> c;
    string s;
    cin >> s;
    n = s.size();
    ans = "";
    ans += s[0];
    for (int i = 1; i <= n - 2; i++) {
        if (s[i] == '-') {
            char l = s[i - 1], r = s[i + 1];

            if (!(((l >= '0' && l <= '9') && (r >= '0' && r <= '9')) ||
                  ((l >= 'a' && l <= 'z') && (r >= 'a' && r <= 'z')) ||
                  ((l >= 'A' && l <= 'Z') && (r >= 'A' && r <= 'Z')))) {
                ans += '-';
                continue;
            }
            string res = "";
            // res += l;
            if (l == r - 1) {
                // ans += l;
                // ans += r;
            } else if (l >= r) {
                // ans += l;
                ans += '-';
                // ans += r;
            } else if (l < r - 1) {
                if (a == 1) {
                    for (char j = l + 1; j <= r - 1; j++) {
                        for (int k = 1; k <= b; k++) {
                            res += toLow(j);
                        }
                    }
                } else if (a == 2) {
                    for (char j = l + 1; j <= r - 1; j++) {
                        for (int k = 1; k <= b; k++) {
                            res += toUp(j);
                        }
                    }
                } else if (a == 3) {
                    for (char j = l + 1; j <= r - 1; j++) {
                        for (int k = 1; k <= b; k++) {
                            res += '*';
                        }
                    }
                }
                if (c == 1) {
                    ans += res;
                } else if (c == 2) {
                    reverse(res.begin(), res.end());
                    ans = ans + res;
                }
            }
        } else {
            ans += s[i];
        }
    }
    ans += s[n - 1];
    cout << ans << endl;
    return 0;
}

NOIP2009 多项式输出

这个题目需要考虑的情况也是挺多的,最好的处理办法是对多项式的第一项和最后一项进行单独处理,而中间的很多项则可以使用一个for循环进行处理
特殊情况1:系数为1或者-1,注意此时如果不是常数项,则不输出;是常数项,直接输出
特殊情况2:总的多项式中只含有一个常数项,此时无论正负都可以直接输出;但是要注意-1的情况
特殊情况3:指数为1的项,多项式中只有一个x,这一点题目中没有给出
其他的情况可以通过测试样例发现,代码中有注释

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
int n, m, ans;
string res = "";
int a[N];
int main() {
    sf(n);
    for (int i = 1; i <= n + 1; i++) {
        cin >> a[i];
    }
    bool flg = true;  // 注意这里有一个特殊情况,只有一个常数项
    for (int i = 1; i <= n; i++) {
        if (a[i] != 0) {
            flg = false;
            break;
        }
    }
    if (a[1] > 0) {  // 先判断第一个
        if (a[1] != 1)
            res += to_string(a[1]);  // 注意系数为1的情况
        res += "x^";
        res += to_string(n);
    } else if (a[1] < 0) {
        // res += '-';
        if (a[1] != -1)
            res += to_string(a[1]);
        else if (a[1] == -1)
            res += '-';
        res += "x^";
        res += to_string(n);
    }
    m = n;
    for (int i = 2; i <= n; i++) {
        if (a[i] > 0) {
            res += '+';
            if (a[i] != 1)
                res += to_string(a[i]);
            if (m == 2) {
                res += 'x';
                continue;
            }
            res += "x^";
            res += to_string(m - 1);
        } else if (a[i] < 0) {
            // res += '-';//如果是负数,则需要把这个给去掉
            if (a[i] != -1)
                res += to_string(a[i]);
            else if (a[i] == -1)
                res += '-';
            if (m == 2) {
                res += 'x';
                continue;
            }
            res += "x^";
            res += to_string(m - 1);
        }
        m--;  // 注意对于每一个系数,都要减一
    }
    if (a[n + 1] > 0) {  // 这是最后一个
        if (flg == false)  // 注意这里有一个特殊情况,只有一个常数项
            res += '+';
        res += to_string(a[n + 1]);
    } else if (a[n + 1] < 0) {
        // res += '-';
        res += to_string(a[n + 1]);
    }
    de(res);
    return 0;
}

NOIP2010 机器翻译

这个题目感觉比前面两个简单,因为可以使用队列先进先出的性质。但是需要注意的一点式,queue中没有像map或者set的find()函数,可以再开一个queue队列,在遍历其中一个队列的时候边遍历边pop()弹出,最后再把存储的元素按照原来的顺序弹入到queue中
代码中有注释

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
int n, m, ans;
string res = "";
int a[N];
queue<int> q;
bool findMy(int x) {
    bool flg = false;
    int cnt = q.size();
    queue<int> q1;  // 另建立一个容器
    for (int i = 0; i < cnt; i++) {
        if (q.front() == x) {
            flg = true;
        }
        q1.push(q.front());
        q.pop();
    }
    // 再把里面的元素返还到queue中
    for (int i = 0; i < cnt; i++) {
        q.push(q1.front());
        q1.pop();
    }
    return flg;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        sf(a[i]);
    }

    ans++;
    q.push(a[1]);
    for (int i = 2; i <= m; i++) {
        if (findMy(a[i]) == true)
            continue;
        else {
            ans++;
            if (q.size() < n) {  // 如果容量此时小于n,则可以继续往里面放
                q.push(a[i]);
            } else {  // 否则,移除队首元素
                q.pop();
                q.push(a[i]);
            }
        }
    }
    de(ans);
    return 0;
}

枚举

NC16593 [NOIP2011]铺地毯

一般二维上的题目,最终都会转换为一维来进行处理。
对于每一个地毯,可以根据他的左下角坐标和长宽,得到两个区间(一维):也就是地毯在x轴和y轴上能覆盖的范围。
只要某一个点的x坐标和y坐标同时在这两个区间中,说明一定被这个地毯所覆盖。
同时我们可以倒着进行遍历,因为题目中要找的是最上面的地毯。如果出现,则直接输出。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e4 + 10;
int n, m;
int ans;
struct E {
    int u[5];
} e[N];
int main() {
    cin >> n;
    int x, y, l, r;
    for (int i = 1; i <= n; i++) {
        sf(x) sf(y) sf(l) sf(r);
        e[i].u[1] = x;
        e[i].u[2] = x + l;
        e[i].u[3] = y;
        e[i].u[4] = y + r;
    }
    cin >> x >> y;
    ans = -1;
    // 从后往前找,如果此时找到了,直接输出
    for (int i = n; i >= 1; i--) {
        if ((x >= e[i].u[1] && x <= e[i].u[2]) &&
            (y >= e[i].u[3] && y <= e[i].u[4])) {
            ans = i;
            break;
        }
    }
    de(ans);
    return 0;
}

NC16438 回文日期

根据题目给出的两个范围,进行for循环遍历,每次加1:
1、需要判断是否为日期:month在[1,12],day在[1,每个月的天数],注意2月需要根据闰年和平年来进行判断
2、判断是否为回文数
代码中有详细注释

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e5 + 10;
// int n, m, ans;
int ans;
bool notDate(int x) {  // 判断是否为题目输入的起始-终止之间的合法日期
    int y, m, d;  // 1、获取年月日
    y = x / 10000;
    m = (x / 100) % 100;
    d = x % 100;

    // 2、根据【年】得到每月的具体天数
    int month[13] = {-1, 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))  // 判断是否为闰年
        month[2] = 29;
    else
        month[2] = 28;

    if (m < 1 || m > 12)  // 注意月份是1-12
        return 0;
    // 3、判断【日】是否符合【年】和【月】的限制条件
    if (d < 1 || d > month[m])
        return 0;
    return 1;
}
bool fun(string x) {  // 判断这个8位的字符串是否为回文数
    for (int i = 0; i < 4; i++) {
        if (x[i] != x[7 - i])
            return 0;
    }
    return 1;
}
int main() {
    int x, y;
    cin >> x >> y;
    string u;
    ans = 0;
    for (int i = x; i <= y; i++) {
        if (!notDate(i))
            continue;
        u = to_string(i);  // 把这个合法的日期转换为字符串

        if (fun(u))
            ans++;
    }
    de(ans);
    return 0;
}

前缀和、差分

NC16649 校门外的树

这个题目需要用到前缀和思想,(暴力进行遍历的话不知道会不会超时,100*10000)
所有位置置0,在输入题目将要移除树木的区间时,将u[l]-1,将u[r]+1(这个地方不解释,前缀和思想)
最终一次遍历,如果遇到0的位置,说明这个地方的树木没有被移除;如果遇到小于0(可能是-1,-2,-3……)的情况,说明被移除了不只一次,我们不用管具体的数量,只要小于0,就说明被移除了

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define de(x) cout << x << " ";
#define Pu puts("");
#define sf(x) scanf("%d", &x);
const int N = 1e4 + 10;
// int n, m, ans;
int len, n;
int u[N];
int main() {
    cin >> len >> n;
    int l, r;
    for (int i = 1; i <= n; i++) {
        cin >> l >> r;
        u[l]--;
        u[r + 1]++;
    }
    int ans = 0;
    if (u[0] == 0)
        ans++;
    for (int i = 1; i <= len; i++) {
        u[i] += u[i - 1];
        if (u[i] == 0)
            ans++;
    }
    de(ans);
    return 0;
}

NC24636 值周

这个题目注意范围是1e8,而不是1e9
我第一次做的时候看成了1e9,甚至用map会超时

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e8 + 5;
int n, m, ans;
// map<int, int> a;
ll a[N];
int main() {
    cin >> n >> m;
    int l, r;
    for (int i = 1; i <= m; i++) {
        sf(l) sf(r);
        a[l]--;
        a[r + 1]++;
    }
    ans = 0;
    if (a[0] == 0)
        ans++;
    // de(99);
    for (int i = 1; i <= n; i++) {
        a[i] += a[i - 1];
        if (a[i] == 0)
            ans++;
    }
    de(ans);
    return 0;
}

NC19913 [CQOI2009]中位数图

这个题目很巧妙的一点是,因为题目只关注相对大小这个概念,所以我们只需要把比k大的数置为1,把比k小的数置为-1
1、只看k左边的数:向k的左边进行遍历,求解“前缀和”,如果等于0,说明此时大于和小于的数量相等。
2、只看k左边的数:向k的右部进行遍历,同上一步
3、同时看左边和右边的数:同样的,在求解“前缀和(定义为sum)”的过程中,如果到右边某处时sum值为m,则我们只需要找左边sum值为-m的数量

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 5;
int n, m, ans;
int a[N];
int b[N * 2];
int main() {
    cin >> n >> m;
    int x, id;
    for (int i = 1; i <= n; i++) {
        sf(x);
        if (x > m)
            a[i] = 1;
        else if (x < m)
            a[i] = -1;
        else if (x == m)
            id = i;
    }
    ans = 0;
    int sz = 0;  // 向左
    for (int i = id - 1; i >= 1; i--) {
        sz += a[i];
        b[n + sz]++;  // 左右两边都有的情况
        if (sz == 0)
            ans++;
    }
    sz = 0;  // 向右
    for (int i = id + 1; i <= n; i++) {
        sz += a[i];
        ans += b[n - sz];  // 左右两边都有的情况
        // 相当于n+(-sz),此时是找左边和它相等数量相反的数字
        if (sz == 0)
            ans++;
    }
    de(ans + 1);
    return 0;
}

NC20032 激光炸弹

这个题目需要注意的是,对于覆盖范围为r的炸弹,其实它的四条边的覆盖长度为r+1,而我们在进行前缀和求解时,往前减去的面积正好是r(写代码的时候会注意到)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e3 + 5;
int n, m, ans;
int u[N][N];
int a[N][N];
int main() {
    cin >> n >> m;
    int x, y, z;
    int len = m;
    for (int i = 1; i <= n; i++) {
        sf(x) sf(y) sf(z);
        x++;  // 注意这里的一步,把所有坐标从1开始
        y++;
        len = max(max(len, x), y);  // 获得最大边界
        a[x][y] = z;
    }
    for (int i = 1; i <= len; i++) {  // 进入二维前缀和求解
        for (int j = 1; j <= len; j++) {
            u[i][j] = a[i][j] + u[i - 1][j] + u[i][j - 1] - u[i - 1][j - 1];
        }
    }
    ans = 0;
    int t;
    // 注意这里的爆炸范围,为r时代表能覆盖(r+1)^2范围内的所有点
    // 但是由于边界上不行,所以直接就是r
    for (int i = m; i <= len; i++) {
        for (int j = m; j <= len; j++) {
            t = u[i][j] - u[i - m][j] - u[i][j - m] + u[i - m][j - m];
            if (t > ans)  // 最大值更新
                ans = t;
        }
    }
    de(ans);
    return 0;
}

NC207053 二分

这个题目很巧妙的一点是,把每个猜数的结果式子,转换为对区间的操作,进一步可以转换为区间上的前缀和(即只需要操作区间起始的两个位置,而后直接累加)
具体细节和注释见代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e3 + 5;
int n, m, ans;
map<int, int> a;
int main() {
    cin >> n;
    int x;
    char y;
    for (int i = 1; i <= n; i++) {
        cin >> x >> y;
        if (y == '.') {  // 转换为区间问题
            a[x]++;
            a[x + 1]--;  // 等于,只有一个点满足
        } else if (y == '+') {
            a[0]++;
            a[x]--;  // 大于,这个点前面的所有点均满足这个条件

        } else if (y == '-') {
            a[x + 1]++;  // 小于,这个点后面的所有点均满足这个条件
        }
    }
    ans = 0;
    int sz = 0;
    for (auto t : a) {
        sz += t.second;  // 进行遍历,当前点的值表示当前最多有几个条件满足
        if (sz > ans) {
            ans = sz;
        }
    }
    de(ans);
    return 0;
}

状压枚举

NC235250 牛可乐的翻转游戏

这个题目需要用到状态压缩和枚举,详细思路见代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 5e2 + 5;
int n, m, ans;
int change[N], pointer[N];
int getNum1(int x) {
    int res = 0;
    while (x) {
        if (x % 2 == 1)
            res++;
        x >>= 1;
    }
    return res;
}
int fun(int x[]) {
    int res = (1 << 31) - 1;

    // 第0行进行一遍全部可能的翻转
    for (int i = 0; i < (1 << m); i++) {
        change[0] = i;  // 第0行即将进行的操作
        int cnt = 0;
        pointer[0] = x[0];  // 这一句不能少,因为后面需要用到第0行的起始状态
        for (int j = 0; j < n; j++) {
            // 第j行进行翻转
            cnt += getNum1(change[j]);

            // 第j行翻转后会影响这一行的棋子颜色:
            // 因为change[j]数组为1的地方说明需要翻转,所以先处理当前操作
            // 然后处理当前操作后所影响的【左】和【右】两种情况
            // 之所以用异或,因为1-0得到1,1-1得到0,正好和棋子翻转的情况一致
            pointer[j] = pointer[j] ^ change[j] ^ (change[j] >> 1) ^ ((change[j] << 1) & ((1 << m) - 1));
            // 下一行所需要进行的操作位置:即为当前行中1的位置
            change[j + 1] = pointer[j];
            // 下一行的此时状态
            pointer[j + 1] = x[j + 1] ^ change[j];
        }
        if (pointer[n - 1] == 0) {
            res = min(res, cnt);
        }
    }
    return res;
}
int black[N], white[N];
int main() {
    cin >> n >> m;
    char temp;
    // 参考一个博主的思路,用两个数组进行存储
    // 直接调用一个函数进行求解
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> temp;
            if (temp == '1') {
                black[i] = (black[i] << 1) + 1;
                white[i] = white[i] << 1;
            } else {
                black[i] = black[i] << 1;
                white[i] = (white[i] << 1) + 1;
            }
        }
    }
    ans = min(fun(black), fun(white));
    if (ans > n * m) {
        de("Impossible\n");
    } else {
        de(ans);
    }
    return 0;
}

指针优化

NC23053 月月查华华的手机

DP,巧妙的从后往前进行数组的赋值
需要注意的是字串,只要从主串中挑选出几个字符能够形成某个串即可
本题目参考思路链接:
https://zhuanlan.zhihu.com/p/616443324点击跳转

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e6 + 9;
int n, m, ans;
char s[N], u[N];
int dp[N][30], now[30];
int main() {
    memset(dp, -1, sizeof(dp));
    memset(now, -1, sizeof(now));

    scanf("%s", s + 1);
    for (int i = strlen(s + 1); i >= 0; i--) {
        // 注意这里的下标临界点需要含有0(无实际意义)
        // 因为dp[i][j]代表i之后的,不包括i
        for (int j = 0; j < 26; j++) {
            dp[i][j] = now[j];
            // i之后距离最近的字符(j+'a')的下标
            // 记得需要倒过来
        }
        now[s[i] - 'a'] = i;
    }
    int T;
    cin >> T;
    bool flag;
    while (T--) {
        scanf("%s", u);
        flag = true;
        int id = 0;
        for (int i = 0; i < strlen(u); i++) {
            if (dp[id][u[i] - 'a'] == -1) {
                flag = false;
                break;
            } else {
                id = dp[id][u[i] - 'a'];
            }
        }
        cout << (flag ? "Yes" : "No") << endl;
    }
    return 0;
}

一般贪心

NC16783 拼数

原本思路是:冒泡排序,规则是将两个字符串按位比较,大的放前
如果较短的那个是较长的那个的子串,则把较长的放在前面

看下面这个样例
99911 999 87
99977 999 66
按照上面的思路,得到的结果应该是
99911 999 87(错误,应为 999 87 99911)
99977 999 66

所以去看了题解,原来可以直接对合成的两种情况进行判断
我当时怎么就没有想到呢,,,

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e6 + 9;
int n, m, ans;
string u[25];
bool fun(int x, int y) {
    string l = "";
    l = u[x] + u[y];
    string r = "";
    r = u[y] + u[x];
    // return l < r;

    for (int i = 0; i < u[x].size() + u[y].size(); i++) {
        if (l[i] < r[i])
            return true;
        else if (l[i] > r[i]) {  // 注意还有这个判断
            // 因为如果第一个元素出现了l比r大,那么直接就说明
            // l放在前面更大
            return false;
        }
    }
    return false;
}
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> u[i];
    }
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (fun(i, j)) {  // 把x放在前面更小,所以进行换位
                string t = u[i];
                u[i] = u[j];
                u[j] = t;
            }
        }
    }
    for (int i = 0; i < n; i++) {
        cout << u[i];
    }
    return 0;
}

NC16618 排座椅

注意这个题目很离谱的一个地方是,输出结果时,需要按照1-n的顺序来

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 2e3 + 9;
int n, m, ans;
int k, l, d;
struct E {
    int u = 0;   // 能拆开的对数
    int id;      // 所处的位置
} f1[N], f2[N];  // 第0维度表示行,第1维度表示列

int u1[N], u2[N];
bool cmp(E a, E b) {
    return a.u > b.u;
}
int main() {
    cin >> m >> n >> k >> l >> d;
    int x, y, w, z;
    for (int i = 1; i <= d; i++) {
        cin >> x >> y >> w >> z;
        if (x == w) {  // 在列开辟通道
            f2[min(y, z)].u++;
            f2[min(y, z)].id = min(y, z);
        } else {  // 在行开辟通道
            f1[min(x, w)].u++;
            f1[min(x, w)].id = min(x, w);
        }
    }
    sort(f1 + 1, f1 + n + 1, cmp);  // 优先放置拆散对数比较多的
    sort(f2 + 1, f2 + m + 1, cmp);
    // 注意必须输出k个行和l个列,是0也需要输出
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        u1[++cnt] = f1[i].id;
        k--;
        if (k == 0) {  // 或者此时次数已经使用完了
            break;
        }
    }
    sort(u1 + 1, u1 + cnt + 1);
    for (int i = 1; i <= cnt; i++) {
        de(u1[i]);
    }
    Pu;
    cnt = 0;
    for (int i = 1; i <= m; i++) {
        u2[++cnt] = f2[i].id;
        l--;
        if (l == 0) {
            break;
        }
    }
    sort(u2 + 1, u2 + cnt + 1);
    for (int i = 1; i <= cnt; i++) {
        de(u2[i]);
    }
    Pu;
    return 0;
}

NC200190 矩阵消除游戏

这个题目思路很巧妙

参考的题解链接

具体细节见代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e2 + 9, mod = 1e9 + 7;
int n, m;
int a[N][N];
int line[N];
int cnt_line;
int cal(int x) {
    int res = 0;
    for (int i = 0; i < n; i++) {
        if (x % 2) {
            cnt_line++;  // 表示取了多少个行
            for (int j = 0; j < m; j++) {
                res += a[i][j];
            }
        } else {
            // 这个时候这个行没有被取,所以加到列中
            for (int j = 0; j < m; j++) {
                line[j] += a[i][j];
            }
        }
        x /= 2;
    }
    return res;
}
int main() {
    int k;
    cin >> n >> m >> k;
    int sz = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> a[i][j];
            sz += a[i][j];
        }
    }
    if (k >= m || k >= n) {
        cout << sz;  // 这个时候直接全部取
        return 0;
    }
    int ans = 0;
    int res;

    for (int i = 0; i < (1 << n); i++) {
        cnt_line = 0;
        memset(line, 0, sizeof(line));
        res = cal(i);  // 取二进制表示的行

        if (k - cnt_line < 0 || k - cnt_line > m)
            continue;  // 注意这一句必须要有,否则就会把所有的都会加上

        sort(line, line + m, greater<int>());
        // 得到除去这些行后的列
        for (int j = 0; j < k - cnt_line; j++) {
            res += line[j];
        }
        ans = max(ans, res);
    }
    cout << ans << endl;
    return 0;
}

NC23036 华华听月月唱歌

注意这个题目很坑的一点是,有可能存在数据区间[l,r]
其中l或者r大于总长度n
(这个坑我调试了很久很久QAQ)

具体细节见代码

#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 9;
int n, m, ans;
struct E {
    int l, r;
} e[N];
bool cmp(E a, E b) {
    if (a.l == b.l) {
        return a.r > b.r;
    }
    return a.l < b.l;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> e[i].l >> e[i].r;
    }
    sort(e + 1, e + m + 1, cmp);
    ans = 1;
    // 注意这里需要判断一下,因为可能会出现没有从1开始的区间
    if (e[1].l != 1) {
        cout << "-1\n";
        return 0;
    }
    int last = e[1].r;  // 已经选了第一个,且根据排序规则,l相同的,r大的排在前面
    int rTmp = last;
    for (int i = 2; i <= m; i++) {
        if (e[i].l <= last + 1) {
            rTmp = max(e[i].r, rTmp);
        } else {
            if (rTmp <= last) {  // 没有更新,说明断层了
                cout << "-1\n";
                return 0;
            }
            last = rTmp;
            i--;  // 后退一个
            ans++;
        }
        // 第8个样例一直过不了,后来参考别人提交的AC代码后发现
        // 存在给的数据区间[l,r],其中l或者r大于n
        if (last >= n) {
            cout << ans << endl;
            return 0;
        }
    }
    if (rTmp > last) {
        ans++;
        last = rTmp;
    }
    if (last == n) {
        cout << ans << endl;
    } else {
        cout << "-1\n";
    }
    return 0;
}

推公式

NC16561 国王的游戏

这是第三次写这个题目了。具体思路看代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 2e4 + 9, mod = 1e9 + 7;
int n, m;
int cheng[N], chu[N], ans[N];
struct E {
    int l, r;
} e[N];
bool cmp(E a, E b) {
    return (a.l * a.r < b.l * b.r);
}
void times(int x) {  // 大数乘法
    int t = 0;
    for (int i = 1; i <= cheng[0]; i++) {
        t = cheng[i] * x + t;
        cheng[i] = t % 10;
        t /= 10;
    }
    int id = cheng[0];
    while (t) {
        cheng[++id] = t % 10;
        t /= 10;
    }
    cheng[0] = id;
}
void divition(int x) {            // 大数除法(向下取整)
    memset(chu, 0, sizeof(chu));  // 除数需要置为0
    int t = 0;
    for (int i = cheng[0]; i >= 1; i--) {
        t = t * 10 + cheng[i];
        chu[i] = t / x;
        if (chu[0] == 0 && chu[i] != 0) {
            chu[0] = i;  // 如果当前商为0,并且得到的商不为0
            // 此时i即为最后商的位数
        }
        t %= x;
    }
}
bool compare() {
    if (ans[0] == chu[0]) {
        for (int i = chu[0]; i >= 1; i--) {
            if (chu[i] > ans[i])
                return 1;
            if (chu[i] < ans[i]) {
                return 0;
            }
        }
    }
    if (ans[0] < chu[0])
        return 1;
    if (ans[0] > chu[0]) {
        return 0;
    }
    return -1;
}
void cp() {
    ans[0] = chu[0];
    for (int i = chu[0]; i >= 1; i--) {
        ans[i] = chu[i];
    }
}
int main() {
    cin >> n;
    n++;
    for (int i = 1; i <= n; i++) {
        cin >> e[i].l >> e[i].r;
    }
    sort(e + 2, e + n + 1, cmp);  // 注意国王的位置不变
    cheng[0] = 1;                 // 位数
    cheng[1] = 1;                 // 因为是乘法,所以用1乘以其他的数
    for (int i = 2; i <= n; i++) {
        times(e[i - 1].l);  // 乘以它前面人的左手数
        divition(e[i].r);   // 除以自己的右手数
        if (compare()) {    // 如果大于当前存储的最大值,则更换
            cp();
        }
    }
    for (int i = ans[0]; i >= 1; i--) {
        printf("%d", ans[i]);
    }
    return 0;
}

NC25043 Protecting the Flower

刚开始我的思路就是对的,先假想只有两个
但是我在处理时间的时候,弄成了累积时间!
#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
struct E {
    int l, r;
    double ri;
} e[N];
bool cmp(E a, E b) {
    return a.l * b.r < a.r * b.l;
}
int main() {
    cin >> n;
    ll u = 0;
    for (int i = 1; i <= n; i++) {
        sf(e[i].l) sf(e[i].r);
        e[i].ri = 1.0 * e[i].l / e[i].r;
        u += e[i].r;
    }
    sort(e + 1, e + n + 1, cmp);
    ll res = 0;
    u -= e[1].r;
    for (int i = 2; i <= n; i++) {
        res += e[i - 1].l * u * 2;
        u -= e[i].r;
    }
    de(res);
    return 0;
}

按位贪心

NC18979 毒瘤xor

这个题目我刚开始的思路也是对的,但是没有考虑到使用前缀和就卡住了
因为是异或。所以从每一位上来看,如果当前区间的所有数字在这一位上的1多
那么我们可能优先把这一位放0,因为是求和,所以异或后这一位为1的数字就会更多
反之如果是0多,那么放1

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define sf(x) scanf("%d", &x);
#define de(x) cout << x << " ";
#define Pu puts("");
const int N = 1e5 + 9, mod = 1e9 + 7;
int n, m, ans;
int a[N][35];    // a[i][j]表示第i个数的第j位是否为1
int pre[N][35];  // pre[i][j]表示前i和数字的第j位为1的个数
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        sf(a[i][0]);
        for (int j = 1; j <= 31; j++) {
            a[i][j] = a[i][0] >> (j - 1) & 1;
            // 求解第i个数字的第j位上是否为1
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= 31; j++) {
            pre[i][j] += a[i][j] + pre[i - 1][j];
            // 求解前i个数字的第j位上为1的个数
        }
    }
    int q, l, r;
    cin >> q;
    int cnt;  // 统计这个区间上第i为是1的个数
    int x;
    while (q--) {
        sf(l) sf(r);
        x = 0;
        for (int i = 1; i <= 31; i++) {
            cnt = pre[r][i] - pre[l - 1][i];
            if (cnt < (r - l + 1 - cnt)) {  // 如果1的个数小于0的个数,当前位置1
                x += 1 << (i - 1);
            }  // 如果1的个数大于0的个数,当前位置0
            // 如果1的个数等于0的个数,因为题目要求同等条件下输出最小的,所以置0
        }
        de(x) Pu;
    }
    return 0;
}

NC20860 兔子的区间密码

这个题目很巧妙,是一种按位贪心。如果当前位不满足,则继续往下找。

#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
inline ll read() {  // 快速快写
    ll x = 0, y = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') {
            y = -y;
        }
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }
    return x * y;
}
int main() {
    int T;
    cin >> T;
    ll a, b;
    ll a_cnt, b_cnt;
    while (T--) {
        a = read();
        b = read();
        // de(a) de(b) Pu;
        if (a == b) {  // 特判:如果a等于b,则直接输出0
            printf("0\n");
            continue;
        }
        a_cnt = (ll)log2(a);  // 得到a转换为二进制有多少位-1
        b_cnt = (ll)log2(b);
        while (a_cnt == b_cnt && a != 0) {
            a -= (1ll << a_cnt);
            b -= (1ll << b_cnt);
            a_cnt = (ll)log2(a);  // 得到a转换为二进制有多少位-1
            b_cnt = (ll)log2(b);
        }  // 找到第一个首位不同位置,如110和1101
        // 那么此时111和1000一定包含在里面
        // de(b_cnt) Pu;
        printf("%lld\n", ((1ll << (b_cnt + 1)) - 1));
        // 这里为什么是b_cnt呢?假想一种情况,l和r只有最后一位不一样,那么此时b_cnt还是1
        // 但是a_cnt却是log2(0)!!!
    }
    return 0;
}

NC17857 起床困难综合症

因为题目只需要输出最终的最大值,所以我们设置两个全0和全1的数
从高到低位进行每行的操作,相当于遍历了所有的情况
如果某一位最终由0变为了1,那么最终的结果包含这一位
如果某一位最终由1变为了0,那么这一位在满足小于m的情况下,最终结果也包含这一位

#include <math.h>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
int u[2];
int main() {
    cin >> n >> m;
    u[1] = -1;  // 全部位为1

    string op;  // 输入的每行操作
    int x;
    while (n--) {
        cin >> op >> x;
        // de(op) de(x) Pu;
        if (op[0] == 'A') {
            u[0] &= x;
            u[1] &= x;
        } else if (op[0] == 'O') {
            u[0] |= x;
            u[1] |= x;
        } else {
            u[0] ^= x;
            u[1] ^= x;
        }
    }
    ans = 0;
    for (int i = 1 << 30; i; i >>= 1) {
        if (u[0] & i) {
            ans |= i;
        } else if (u[1] & i && i <= m) {  // 注意这里应该是else if
            // 因为如果我们在0-->1满足的情况下,肯定优先使用这个
            // 而不是使用1-->1,因为后者是使得m变小
            ans |= i;
            m ^= i;
            // 这里比如有一种情况是1011……,那么此时我们处理完高位后
            // 需要把最高位置0,防止第二位是1的二进制数满足条件
        }
    }
    printf("%d\n", ans);
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值