2025.7.20

刷题日记

写了两道1600,一道单切一遍过,另一道看了题解改了好几次过了,还行


Codeforces Round 1003 (Div. 4) E. Skibidus and Rizz
https://codeforces.com/contest/2065/problem/E

这道题要求用n个0和m个1构造一个长度为n + m的二进制字符串
要求该字符串的所有连续子串的最大平衡值为k
平衡值为该子串中0的个数和1的个数的差值(绝对值)

对题意分析,首先我们可以知道,如果k == std::max(n, m),则一定可以满足
此时我们只需要先输出所有0,再输出所有1
这样的字符串,平衡值最大的地方就在连续的0或者连续的1那里

如果k > std::max(n, m),则一定不可以
那我k甚至比0或者1的个数都大,现在要求01个数之差去等于k,根本不可能
这个时候可以直接输出-1并return

那么这个时候我们需要考虑k < std::max(n, m)的情况
为了限制平衡值的最大值,我们需要将01隔开
而为了凑出平衡值k,我们需要取n和m中的较大者做上限
对此我们进行分类讨论,假设n和m中,m大
那么我们需要取m个1和m - k个0,将这取出来的0和1交叉分布,作为平衡值最大的子串
那剩下的n - m + k个字符,我们可以将其平分,左边放一半,右边放一半即可

我承认这道题有点Guess的成分,当时已经做好WA2的准备了
结果一遍过了,确实挺惊讶的(

点击查看代码
#include <bits/stdc++.h>
#include <bits/extc++.h>

#define debug(x) std::cout << #x << " = " << x << '\n'
#define debugp(x, y) std::cout << #x << ", " << #y << " = " << "[" << x << ", " << y << "]\n"
#define debugt(x, y, z) std::cout << #x << ", " << #y << ", " << #z << " = " << "[" << x << ", " << y << ", " << z << "]\n"
#define debug1(f, a, b) std::cout << #f << ": "; for (int i = (a); i <= (b); i++) std::cout << (f)[i] << " \n"[i == (b)]
#define debug2(f, a, b, c, d) std::cout << #f << ":\n"; for (int i = (a); i <= (b); i++) for (int j = (c); j <= (d); j++) std::cout << (f)[i][j] << " \n"[j == (d)]
#define debug_1(q) std::cout << #q << ": ";  for (auto it : (q)) std::cout << it << ' '; std::cout << '\n'
#define debug_2(q) std::cout << #q << ":\n"; for (auto [x, y] : (q)) std::cout << '[' << x << ", " << y << "]\n"
#define debug_3(q) std::cout << #q << ":\n"; for (auto [x, y, z] : (q)) std::cout << '[' << x << ", " << y << ", " << z << "]\n"  
#define show_pq(q) std::cout << #q << ": ";  while ((q).size()) { std::cout << (q).top() << ' '; (q).pop(); } std::cout << '\n'
#define show_gh(g) std::cout << #g << ":\n"; for (int i = 1; i <= n; i++) { std::cout << i << ": "; for (auto it : (g)[i]) std::cout << it << ' '; std::cout << '\n'; }

using i64 = long long;
using pii = std::pair<int, int>;
using namespace __gnu_pbds;
using ordered_set = tree<i64, null_type, std::less<i64>, rb_tree_tag, tree_order_statistics_node_update>;
using ordered_multiset = tree<i64, null_type, std::less_equal<i64>, rb_tree_tag, tree_order_statistics_node_update>;

constexpr int N = 2e5 + 10;
constexpr int inf = INT_MAX;
constexpr i64 INF = LLONG_MAX;
constexpr int mod = 998244353;

/*====================My_Solution====================//

    要求用n个0和m个1构造一个长度为n + m的二进制字符串
    要求该字符串的所有连续子串的最大平衡值为k
    平衡值为该子串中0的个数和1的个数的差值(绝对值)

    首先我们可以知道,如果k == std::max(n, m),则一定可以满足
    如果k > std::max(n, m),则一定不可以;那k < std::max(n, m)呢?
    此时我们需要将01隔开,以限制平衡值的最大值

    为了凑出平衡值k,我们需要取n和m中的较大者做上限
    假设n和m中,m大,那么我们需要取m个1和m - k个0
    将这取出来的0和1交叉分布,作为平衡值最大的子串
    那剩下的n - m + k个字符呢?左右两端各一半?

//====================My_Solution====================*/ 

void solve () {
    int n, m, k;
    std::cin >> n >> m >> k;

    if (k > std::max(n, m)) {
        std::cout << "-1\n";
        return;
    }

    if (k == std::max(n, m)) {
        for (int i = 1; i <= n + m; i++) {
            std::cout << (i <= n ? '0' : '1');
        }
        std::cout << '\n';
        return;
    }

    std::deque<char> ans;
    if (n > m) {
        if (m < n - k) {
            std::cout << "-1\n";
            return;
        }

        // 在n个0中插入n - k个1
        int cnt0 = n;
        int cnt1 = n - k;
        while (ans.size() < n + n - k) {
            if (cnt0) {
                ans.push_back('0');
                cnt0--;
            }
            if (cnt1) {
                ans.push_back('1');
                cnt1--;
            }
        }

        cnt1 = m - n + k;
        while (cnt1) {
            if (cnt1) {
                ans.push_back('1');
                cnt1--;
            }
            if (cnt1) {
                ans.push_front('1');
                cnt1--;
            }
        }
    }
    else {
        if (n < m - k) {
            std::cout << "-1\n";
            return;
        }

        // 在m个1中插入m - k个0
        int cnt1 = m;
        int cnt0 = m - k;
        while (ans.size() < m + m - k) {
            if (cnt1) {
                ans.push_back('1');
                cnt1--;
            }
            if (cnt0) {
                ans.push_back('0');
                cnt0--;
            }
        }

        cnt0 = n - m + k;
        while (cnt0) {
            if (cnt0) {
                ans.push_back('0');
                cnt0--;
            }
            if (cnt0) {
                ans.push_front('0');
                cnt0--;
            }
        }
    }

    for (auto it : ans) {
        std::cout << it;
    }
    std::cout << '\n';
}

int32_t main () {   
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t = 1;
    std::cin >> t;

    while (t--) {
        solve();
    }
    
    return 0;
}


Codeforces Round 995 (Div. 3) E. Best Price
https://codeforces.com/contest/2051/problem/E

给定n个客人,和k个限制,现在对要售卖的物品定单价
每个客人有a和b两个属性,表现如下:

  1. 单价小于a,买 + 留好评
  2. 单价大于a小于b,买 + 留差评
  3. 单价大于b,不买,不做评价
    要求差评不超过k个的情况下,赚取最多的钱

我们需要注意一点,如果价格大于b,那么该顾客就不会购买了
所以,价格定高可以劝退“抠搜”的顾客,也不用担心差评
突然感觉有点像二分最优解,外二分内循环,应该是O(nlogn),理论可行
此处的方案是二分价格,枚举数量

但是我们发现,二分枚举价格的过程并不具有单调性
可能某个价格因为高,劝退了一些抠搜的顾客,赚了更多的钱
也可能因为价格高,导致很多人买不起,或者给差评,不符合要求
由限制条件的特性来看,二分枚举并非正解

我们重新来分析
首先,如果允许O(n ^ 2)的复杂度,我们可以很快写出一个绝对正确的解法
枚举每一个可能的价格,再去枚举每一个客户,可以算出总的最优解
但是显然不能,所以要想一个优化方法
我们需要O(nlogn)甚至更低的复杂度,所以我们需要二分
但是前面的二分枚举不可行,所以我们要考虑在内部二分
也就是枚举价格,二分数量

对于每个价格p,我们可以两个数值

  1. 在p价格下,购买树木的客户数 (p <= b[i])
  2. 在p价格下,会收获到的差评数 (a[i] < p <= b[i])

因此,我们可以先对用户进行排序,然后用lower_bound等方法可以很轻松的计算出第一个数值
但是第二个数值就不是很好计算了,这个时候需要间接的计算
由题意可知,差评数 = 购买数 - 不给差评的数量
而“不给差评的数量”很好计算,因为此时的价格满足p <= a[i]
我们便可以写出如下代码:
(注:这道题需要注意的一点是,枚举价格不需要枚举所有数字,只需要枚举数组a和数组b中出现的价格即可。枚举其他的没有意义)

点击查看代码
#include <bits/stdc++.h>
#include <bits/extc++.h>

#define debug(x) std::cout << #x << " = " << x << '\n'
#define debugp(x, y) std::cout << #x << ", " << #y << " = " << "[" << x << ", " << y << "]\n"
#define debugt(x, y, z) std::cout << #x << ", " << #y << ", " << #z << " = " << "[" << x << ", " << y << ", " << z << "]\n"
#define debug1(f, a, b) std::cout << #f << ": "; for (int i = (a); i <= (b); i++) std::cout << (f)[i] << " \n"[i == (b)]
#define debug2(f, a, b, c, d) std::cout << #f << ":\n"; for (int i = (a); i <= (b); i++) for (int j = (c); j <= (d); j++) std::cout << (f)[i][j] << " \n"[j == (d)]
#define debug_1(q) std::cout << #q << ": ";  for (auto it : (q)) std::cout << it << ' '; std::cout << '\n'
#define debug_2(q) std::cout << #q << ":\n"; for (auto [x, y] : (q)) std::cout << '[' << x << ", " << y << "]\n"
#define debug_3(q) std::cout << #q << ":\n"; for (auto [x, y, z] : (q)) std::cout << '[' << x << ", " << y << ", " << z << "]\n"  
#define show_pq(q) std::cout << #q << ": ";  while ((q).size()) { std::cout << (q).top() << ' '; (q).pop(); } std::cout << '\n'
#define show_gh(g) std::cout << #g << ":\n"; for (int i = 1; i <= n; i++) { std::cout << i << ": "; for (auto it : (g)[i]) std::cout << it << ' '; std::cout << '\n'; }

using i64 = long long;
using pii = std::pair<i64, i64>;
using namespace __gnu_pbds;
using ordered_set = tree<i64, null_type, std::less<i64>, rb_tree_tag, tree_order_statistics_node_update>;
using ordered_multiset = tree<i64, null_type, std::less_equal<i64>, rb_tree_tag, tree_order_statistics_node_update>;

constexpr int N = 2e5 + 10;
constexpr int inf = INT_MAX;
constexpr i64 INF = LLONG_MAX;
constexpr int mod = 998244353;

/*====================My_Solution====================//
  猜想:
    给定n个客人,和k个限制
    每个客人有a和b两个属性,小于a则买,留好评;大于a小于b则买,留差评
    要求差评不超过k个的情况下,赚取最多的钱

    那我们可以对这个结构体数组排个序,让最“抠搜”的顾客排在前面
    也就是a小的往前排,如果a一样,把b大的往前排
    我们要让前k个顾客都买但差评,应该是最优解

    我们忘了一茬,如果价格大于b,那么该顾客就不会购买了
    所以,价格定高可以劝退抠搜的顾客,也不用担心差评
    突然感觉有点像二分最优解,外二分内循环,应该是O(nlogn),理论可行

    此时问题来到了“二分该怎么写”
    当满足k的条件时,不一定是因为定价低,也可能是因为定价高,劝退了一些顾客
    理论上,我们既需要向左扩展,又需要向右扩展,此时就不是二分了

  题解:
    首先,如果允许O(n ^ 2)的复杂度,我们可以很快写出一个绝对正确的解法
    枚举每一个可能的价格,再去枚举每一个客户,可以算出总的最优解
    但是显然不能,所以要想一个优化方法

    对于每个价格p,我们计算两个数值
    1. 在p价格下,购买树木的客户数 (p <= b[i])
    2. 在p价格下,会收获到的差评数 (a[i] < p <= b[i])

    我们先对用户进行排序,然后用lower_bound等方法可以很轻松的计算出第一个数值
    但是第二个数值就不是很好计算了,这个时候需要间接的计算
    差评数 = 购买数 - 不给差评的数量!
    而“不给差评的数量”很好计算,因为此时的价格满足p <= a[i]

//====================My_Solution====================*/ 

void solve () {
    i64 n, k;
    std::cin >> n >> k;

    std::vector<i64> a(n + 1), b(n + 1);
    std::vector<i64> price;

    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
        price.push_back(a[i]);
    }
    for (int i = 1; i <= n; i++) {
        std::cin >> b[i];
        price.push_back(b[i]);
    }
    std::sort(a.begin() + 1, a.end());
    std::sort(b.begin() + 1, b.end());
    std::sort(price.begin() + 1, price.end());

    i64 ans = 0;
    for (auto i : price) {
        i64 res = 0, p1, p2;
        p1 = n - ((lower_bound(b.begin() + 1, b.end(), i) - b.begin()) - 1);
        p2 = p1 - (n - ((lower_bound(a.begin() + 1, a.end(), i) - a.begin()) - 1));

        if (p2 <= k) {
            ans = std::max(ans, p1 * i);
        }
    }
    std::cout << ans << '\n';
}

int32_t main () {   
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t = 1;
    std::cin >> t;

    while (t--) {
        solve();
    }
    
    return 0;
}
posted @ 2025-07-20 20:34  _彩云归  阅读(38)  评论(0)    收藏  举报