反悔贪心

反悔贪心

一般贪心仅能解出局部最优解,而一部分题目不满足局部最优解等价于全局最优解,于是引入反悔操作。

本质思想是每次都贪心进行操作,若以后有更优情况则反悔最劣的操作,即跑一遍局部最优假贪心,再通过反悔操作得到答案。

不难发现反悔贪心的核心在于维护反悔操作所需的值,所以当有些题目可以很方便地维护出操作贡献时,可以考虑反悔贪心。

P2949 [USACO09OPEN] Work Scheduling G

\(n\) 项任务,每一时刻可以完成一项任务,第 \(i\) 项任务的截止时间为 \(d_i\) ,价值为 \(p_i\) ,求最大价值。

\(n \leq 10^5\)

首先按截止时间排序,依次决策每个任务:

  • 若当前任务可以做就先去做。
  • 否则若当前任务价值高于做过的最小价值的任务,则将其替换为该任务。

时间复杂度 \(O(n \log n)\)

类似的题目:P4053 [JSOI2007] 建筑抢修

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;

pair<int, int> a[N];

int n;

signed main() {
    scanf("%d", &n);
        
    for (int i = 1; i <= n; ++i)
        scanf("%d%d", &a[i].first, &a[i].second);
    
    sort(a + 1, a + 1 + n);
    priority_queue<int, vector<int>, greater<int> > q;
    
    for (int i = 1; i <= n; ++i)  {
        if (q.size() < a[i].first)
            q.emplace(a[i].second);
        else if (a[i].second > q.top())
            q.pop(), q.emplace(a[i].second);
    }

    ll ans = 0;
    
    while (!q.empty())
        ans += q.top(), q.pop();
    
    printf("%lld", ans);
    return 0;
}

CF865D Buy Low Sell High

已知接下来 \(n\) 天的股票价格,每一天可以选择买进、卖出或不做,求 \(n\) 天后的最大利润。

\(n \leq 3 \times 10^5\)

考虑如何反悔形如“第 \(i\) 天买入,第 \(j\) 天卖出”的决策,决策的同时引入一个价格为 \(a_j\) 的物品,这样“第 \(i\) 天买入,第 \(j\) 天卖出,同时买入价格为 \(a_j\) 的物品,并在第 \(k\) 天卖出“就等价于“第 \(i\) 天买入,第 \(k\) 天卖出”,实现了反悔操作。

维护一个堆代表可选物品价格,从前向后遍历每一天。对于第 \(i\) 天,找到堆中最小价格 \(a_j\) 并加入 \(a_i\) 代表 \(a_i\) 这一天可选。若 \(a_i > a_j\) ,则把答案加上 \(a_i - a_j\) ,并向集合中再次加入 \(a_i\) ,并删除 \(a_j\)

时间复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int n;

signed main() {
    scanf("%d", &n);
    priority_queue<int, vector<int>, greater<int> > q;
    ll ans = 0;
    
    for (int i = 1; i <= n; ++i) {
        int x;
        scanf("%d", &x);
        
        if (!q.empty() && q.top() < x)
            ans += x - q.top(), q.pop(), q.emplace(x);
        
        q.emplace(x);
    }
    
    printf("%lld", ans);
    return 0;
}

P3545 [POI2012] HUR-Warehouse Store

\(n\) 天,第 \(i\) 天上午会进货 \(a_i\) 件商品,中午会有顾客购买 \(b_i\) 件商品,可选择满足或无视。求最多能够满足多少顾客的需求。

\(n \leq 2.5 \times 10^5\)

每次能选就选,不能选时尝试替换掉原来 \(b_i\) 最大的顾客(这样可以剩下更多商品给后面的顾客),时间复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2.5e5 + 7;

int a[N], b[N];
bool choose[N];

int n;

signed main() {
    scanf("%d", &n);
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);
    
    for (int i = 1; i <= n; ++i)
        scanf("%d", b + i);

    priority_queue<pair<int, int> > q;
    ll surplus = 0;
    
    for (int i = 1; i <= n; ++i) {
        surplus += a[i];
        
        if (surplus >= b[i])
            surplus -= b[i], q.emplace(b[i], i);
        else if (!q.empty() && b[i] < q.top().first)
            surplus += q.top().first - b[i], q.pop(), q.emplace(b[i], i);
    }
    
    printf("%d\n", (int)q.size());
    
    while (!q.empty())
        choose[q.top().second] = true, q.pop();
    
    for (int i = 1; i <= n; ++i)
        if (choose[i])
            printf("%d ", i);
    
    return 0;
}

P1484 种树

\(n\) 个数中选出至多 \(k\) 个两两不相邻的数的最大和。

\(n \leq 3 \times 10^5\)

设当前选出的最大的点为 \(id\) ,它左右两边的点分别是 \(x, y\) ,就删掉这三个点并新建一个点权为 \(a_x + a_y - a_{id}\) 的,这样下次选出这个点 \(p\) 时就实现了反悔操作。同时因为反悔操作舍弃了 \(id\) ,所以之后也不会选 \(id\) ,故正确。用链表维护相邻点即可做到 \(O(n \log n)\)

类似的题目:

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7;

struct List {
    ll val;
    int pre, nxt;
} l[N];

bool vis[N];

int n, k;

signed main() {
    scanf("%d%d", &n, &k);
    priority_queue<pair<ll, int> > q;
    
    for (int i = 1; i <= n; ++i) {
        ll x;
        scanf("%lld", &x);
        l[i] = (List){x, i - 1, i + 1}, q.emplace(x, i);
    }
    
    ll ans = 0;
    
    while (k--) {
        while (vis[q.top().second])
            q.pop();
        
        int val = q.top().first, x = q.top().second;
        q.pop();
        
        if (val <= 0)
            break;
        
        ans += val, vis[l[x].pre] = vis[l[x].nxt] = true;
        
        l[x].val = l[l[x].pre].val + l[l[x].nxt].val - l[x].val;
        l[x].pre = l[l[x].pre].pre, l[x].nxt = l[l[x].nxt].nxt;
        l[l[x].pre].nxt = l[l[x].nxt].pre = x;
        
        q.emplace(l[x].val, x);
    }
    
    printf("%lld", ans);
    return 0;
}

CF436E Cardboard Box

\(n\) 个关卡,每个关卡可以花 \(a_i\) 代价得到一颗星,也可以花 \(b_i\) 代价得到两颗星,也可以不玩。求获得 \(w\) 颗星最少代价。

\(n \leq 3 \times 10^5\)

考虑如何从选了 \(i\) 颗星的方案扩展得到选 \(i + 1\) 颗星的方案:

  • 选一个没有选星星的位置 \(i\) ,付出 \(a_i\) 的代价选一颗星。
  • 选一个已选一颗星的位置 \(i\) ,付出 \(b_i - a_i\) 的代价选两颗星。
  • 选一个已选一颗星的位置 \(i\) ,再选一个没有选星星的位置 \(j\) ,将原来 \(i\) 位置上的星星反悔不选,再在 \(j\) 位置上选两颗星,代价为 $b_j - a_i $。
  • 选一个已选两颗星的位置 \(i\) ,再选一个没有选星星的位置 \(j\) ,将原来 \(i\) 位置上的星星反悔选一颗星星,再在 \(j\) 位置上选两颗星,代价为 \(b_j - (b_i - a_i)\)

用堆维护即可做到 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 3e5 + 7;

ll a[N], b[N];
int tag[N];

int n, w;

struct Heap {
    priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > q;
    
    int op;
    
    inline Heap(int _op) : op(_op) {}
    
    inline void maintain() {
        while (!q.empty() && tag[q.top().second] != op)
            q.pop();
    }
    
    inline bool empty() {
        return maintain(), q.empty();
    }
    
    inline pair<ll, int> top() {
        return maintain(), q.top();
    }
    
    inline void emplace(pair<ll, int> x) {
        q.emplace(x);
    }
} q1(0), q2(1), q3(1), q4(0), q5(2);

inline void update0(int x) {
    tag[x] = 0;
    q1.q.emplace(a[x], x);
    q4.q.emplace(b[x], x);
}

inline void update1(int x) {
    tag[x] = 1;
    q2.q.emplace(b[x] - a[x], x);
    q3.q.emplace(-a[x], x);
}

inline void update2(int x) {
    tag[x] = 2;
    q5.q.emplace(-(b[x] - a[x]), x);
}

signed main() {
    scanf("%d%d", &n, &w);
    
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d", a + i, b + i);
        q1.q.emplace(a[i], i), q4.q.emplace(b[i], i);
    }
    
    ll ans = 0;
    
    while (w--) {
        ll res = inf;
        int op = 0, x, y;
        
        if (!q1.empty() && q1.top().first < res)
            res = q1.top().first, x = q1.top().second, op = 1;
        
        if (!q2.empty() && q2.top().first < res)
            res = q2.top().first, x = q2.top().second, op = 2;
        
        if (!q3.empty() && !q4.empty() && q3.top().first + q4.top().first < res)
            res = q3.top().first + q4.top().first, x = q3.top().second, y = q4.top().second, op = 3;
        
        if (!q5.empty() && !q4.empty() && q5.top().first + q4.top().first < res)
            res = q5.top().first + q4.top().first, x = q5.top().second, y = q4.top().second, op = 4;
        
        ans += res;
        
        if (op == 1)
            update1(x);
        else if (op == 2)
            update2(x);
        else if (op == 3)
            update0(x), update2(y);
        else
            update1(x), update2(y);
    }
    
    printf("%lld\n", ans);
    
    for (int i = 1; i <= n; ++i)
        printf("%d", tag[i]);
    
    return 0;
}

P3045 [USACO12FEB] Cow Coupons G

\(n\) 头奶牛,第 \(i\) 头初始价格为 \(p_i\) ,使用优惠券时价格为 \(c_i\) 。你有 \(k\) 张优惠券和 \(m\) 元,求购买奶牛的最大数量。

\(n \leq 5 \times 10^4\)

能够使买入奶牛数量增加的三种操作:

  • 用优惠券买一头未买的奶牛。
  • 不用优惠券买一头未买的奶牛。
  • 将一头用优惠券买的奶牛替换为不用优惠券买的同一奶牛,并用优惠券买入一头未买的奶牛。

用堆维护即可做到 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 5e4 + 7;

int id[N];

struct Heap {
    priority_queue<pair<int, int> > q;
    
    int op;
    
    inline Heap(int _op) : op(_op) {}
    
    inline void maintain() {
        while (!q.empty() && id[q.top().second] != op)
            q.pop();
    }
    
    inline bool empty() {
        return maintain(), q.empty();
    }
    
    inline pair<int, int> top() {
        return maintain(), q.top();
    }
} q1(0), q2(0), q3(1);

int p[N], c[N];

ll m;
int n, k;

inline void update0(int x) {
    id[x] = 0;
    q1.q.emplace(-p[x], x), q2.q.emplace(-c[x], x);
}

inline void update1(int x) {
    id[x] = 1;
    q3.q.emplace(c[x] - p[x], x);
}

inline void update2(int x) {
    id[x] = 2;
}

signed main() {
    scanf("%d%d%lld", &n, &k, &m);

    for (int i = 1; i <= n; ++i)
        scanf("%d%d", p + i, c + i), update0(i);

    int ans = 0;

    for (;;) {
        ll res = inf, res1 = inf, res2 = inf, res3 = inf;

        if (!q1.empty())
            res = min(res, res1 = -q1.top().first); // add p[x]

        if (k && !q2.empty())
            res = min(res, res2 = -q2.top().first); // add c[x]

        if (!q3.empty() && !q2.empty())
            res = min(res, res3 = -q3.top().first - q2.top().first); // c[i] -> p[i] + c[j]

        if (res > m)
            break;

        m -= res;

        if (res1 == res)
            update2(q1.top().second), ++ans;
        else if (res2 == res)
            update1(q2.top().second), ++ans, --k;
        else
            update2(q3.top().second), update1(q2.top().second), ++ans;
    }

    printf("%d", ans);
    return 0;
}

P5470 [NOI2019] 序列

给定 \(a_{1 \sim n}, b_{1 \sim n}\) ,需要分别对两个序列各指定恰好 \(K\) 个下标,要求至少有 \(L\) 个下标在两个序列中都被指定,最大化这 \(2K\) 个下标在序列中对应的元素的总和。

\(\sum n \leq 10^6\)

首先考虑没有 \(L\) 限制的贪心,从 \(a, b\) 中各选 \(K\) 个最大的即可。

对于 \(L\) 的限制,考虑反悔贪心,每次选取增量尽可能大的操作反悔,使得每次返回后都能使当前同时出现在两个序列中的点数多一。

  • 找一个选了的 \(a_i\) 和一个选了的 \(b_j\) ,用 \(b_i\) 代替 \(b_j\) ,增量为 \(b_i - b_j\)
  • 找一个选了的 \(a_i\) 和一个选了的 \(b_j\) ,用 \(a_j\) 代替 \(a_i\) ,增量为 \(a_j - a_i\)
  • 找一个选了的 \(a_i, b_j\) ,再找一个没选的位置 \(k\) ,选 \(a_k, b_k\) 代替 \(a_i, b_j\) ,增量为 \(a_k + b_k - a_i - b_j\)
  • 找一个选了的 \(a_i, b_j\) ,再找一个都选的位置 \(k\) ,选 \(a_j, b_i\) 代替 \(a_k, b_k\) ,增量是 \(a_j + b_i - a_k - b_k\)

时间复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 2e5 + 7;

ll a[N], b[N];
int tag[N];

int n, k, l;

struct Heap {
    priority_queue<pair<ll, int> > q;

    int op, sign;

    inline Heap(int _op, int _sign) : op(_op), sign(_sign) {}

    inline void update() {
        while (!q.empty() && tag[q.top().second] != op)
            q.pop();
    }

    inline ll topval() {
        return update(), (q.empty() ? -inf : q.top().first) * sign;
    }

    inline int topid() {
        return update(), q.top().second;
    }

    inline void pop() {
        q.pop();
    }

    inline void clear() {
        while (!q.empty())
            q.pop();
    }

    inline void emplace(ll val, int id) {
        q.emplace(val * sign, id);
    }
} q1(1, 1), q2(2, -1), q3(2, 1), q4(1, -1), q5(0, 1), q6(3, -1);

// q1 : 选了 a[i] 后最大的 b[i]
// q2 : 选了 b[i] 后最小的 b[i]
// q3 : 选了 b[i] 后最大的 a[i]
// q4 : 选了 a[i] 后最小的 a[i]
// q5 : 一个没选最大的 a[i] + b[i]
// q6 : 两个都选的最小的 a[i] + b[i]

priority_queue<pair<ll, int> > qa, qb;

inline void update0(int x) {
    tag[x] = 0;
    q5.emplace(a[x] + b[x], x);
}

inline void update1(int x) {
    tag[x] = 1;
    q1.emplace(b[x], x), q4.emplace(a[x], x);
}

inline void update2(int x) {
    tag[x] = 2;
    q2.emplace(b[x], x), q3.emplace(a[x], x);
}

inline void update3(int x) {
    tag[x] = 3;
    q6.emplace(a[x] + b[x], x);
}

signed main() {
    int T;
    scanf("%d", &T);

    while (T--) {
        scanf("%d%d%d", &n, &k, &l);

        while (!qa.empty())
            qa.pop();

        while (!qb.empty())
            qb.pop();

        for (int i = 1; i <= n; ++i)
            scanf("%d", a + i), qa.emplace(a[i], i);

        for (int i = 1; i <= n; ++i)
            scanf("%d", b + i), qb.emplace(b[i], i);

        memset(tag + 1, 0, sizeof(int) * n);
        ll ans = 0;

        while (k--) {
            ans += qa.top().first, tag[qa.top().second] |= 1, qa.pop();
            ans += qb.top().first, tag[qb.top().second] |= 2, qb.pop();
        }

        q1.clear(), q2.clear(), q3.clear(), q4.clear(), q5.clear(), q6.clear();

        for (int i = 1; i <= n; ++i) {
            if (!tag[i])
                update0(i);
            else if (tag[i] == 1)
                update1(i);
            else if (tag[i] == 2)
                update2(i);
            else
                update3(i), --l;
        }
        
        if (l <= 0) {
            printf("%lld\n", ans);
            continue;
        }

        while (l--) {
            ll res = -inf;
            int op = -1;

            if (q1.topval() - q2.topval() > res)
                res = q1.topval() - q2.topval(), op = 1;

            if (q3.topval() - q4.topval() > res)
                res = q3.topval() - q4.topval(), op = 2;

            if (q5.topval() - q2.topval() - q4.topval() > res)
                res = q5.topval() - q2.topval() - q4.topval(), op = 3;

            if (q1.topval() + q3.topval() - q6.topval() > res)
                res = q1.topval() + q3.topval() - q6.topval(), op = 4;

            ans += res;

            if (op == 1) {
                int i = q1.topid(), j = q2.topid();
                q1.pop(), q2.pop();
                update3(i), update0(j);
            } else if (op == 2) {
                int i = q3.topid(), j = q4.topid();
                q3.pop(), q4.pop();
                update3(i), update0(j);
            } else if (op == 3) {
                int k = q5.topid(), i = q2.topid(), j = q4.topid();
                q5.pop(), q2.pop(), q4.pop();
                update3(k), update0(i), update0(j);
            } else if (op == 4) {
                int i = q1.topid(), j = q3.topid(), k = q6.topid();
                q1.pop(), q3.pop(), q6.pop();
                update3(i), update3(j), update0(k);
            }
        }

        printf("%lld\n", ans);
    }

    return 0;
}

[AGC018C] Coins

\(x + y + z\) 个人,第 \(i\) 人有 \(a_i\) 金币、\(b_i\) 银币、\(c_i\) 铜币。要选出 \(x\) 个人获得其金币,\(y\) 个人获得其银币,\(z\) 个人获得其铜币,在选人不重复的情况下求币总数的最大值。

\(x + y + z \leq 10^5\)

考虑先随便找出一种合法的方案,然后反悔操作都是类似于环的重分配操作,一共五种:

  • \(A \to B, B \to C, C \to A\)
  • \(A \to C, C \to B, B \to A\)
  • \(A \to B, B \to A\)
  • \(A \to C, C \to A\)
  • \(B \to C, C \to B\)

开六个堆维护每一步转换的决策即可,时间复杂度 \(O(n \log n)\)

类似的题目:CF730I Olympiad in Programming and Sports

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 1e18;
const int N = 1e5 + 7;

int a[N], b[N], c[N], tag[N];

int x, y, z;

struct Heap {
    priority_queue<pair<ll, int> > q;
    
    int op;
    
    inline Heap(int _op) : op(_op) {}
    
    inline void maintain() {
        while (!q.empty() && tag[q.top().second] != op)
            q.pop();
    }
    
    inline bool empty() {
        return maintain(), q.empty();
    }
    
    inline pair<ll, int> top() {
        return maintain(), q.top();
    }
} qxy(1), qxz(1), qyz(2), qyx(2), qzx(3), qzy(3);

inline void updatex(int x) {
    tag[x] = 1, qxy.q.emplace(b[x] - a[x], x), qxz.q.emplace(c[x] - a[x], x);
}

inline void updatey(int x) {
    tag[x] = 2, qyx.q.emplace(a[x] - b[x], x), qyz.q.emplace(c[x] - b[x], x);
}

inline void updatez(int x) {
    tag[x] = 3, qzx.q.emplace(a[x] - c[x], x), qzy.q.emplace(b[x] - c[x], x);
}

signed main() {
    scanf("%d%d%d", &x, &y, &z);

    for (int i = 1; i <= x + y + z; ++i)
        scanf("%d%d%d", a + i, b + i, c + i);

    ll ans = 0;

    for (int i = 1; i <= x; ++i)
        ans += a[i], updatex(i);

    for (int i = x + 1; i <= x + y; ++i)
        ans += b[i], updatey(i);

    for (int i = x + y + 1; i <= x + y + z; ++i)
        ans += c[i], updatez(i);

    for (;;) {
        pair<ll, int> res = make_pair(-inf, 0);

        if (!qxy.empty() && !qyx.empty())
            res = max(res, make_pair(qxy.top().first + qyx.top().first, 1));

        if (!qyz.empty() && !qzy.empty())
            res = max(res, make_pair(qyz.top().first + qzy.top().first, 2));

        if (!qxz.empty() && !qzx.empty())
            res = max(res, make_pair(qxz.top().first + qzx.top().first, 3));

        if (!qxy.empty() && !qyz.empty() && !qzx.empty())
            res = max(res, make_pair(qxy.top().first + qyz.top().first + qzx.top().first, 4));

        if (!qxz.empty() && !qzy.empty() && !qyx.empty())
            res = max(res, make_pair(qxz.top().first + qzy.top().first + qyx.top().first, 5));

        if (res.first <= 0)
            break;

        ans += res.first;

        if (res.second == 1) {
            int x = qxy.top().second, y = qyx.top().second;
            updatey(x), updatex(y);
        } else if (res.second == 2) {
            int y = qyz.top().second, z = qzy.top().second;
            updatez(y), updatey(z);
        } else if (res.second == 3) {
            int x = qxz.top().second, z = qzx.top().second;
            updatez(x), updatex(z);
        } else if (res.second == 4) {
            int x = qxy.top().second, y = qyz.top().second, z = qzx.top().second;
            updatey(x), updatez(y), updatex(z);
        } else {
            int x = qxz.top().second, z = qzy.top().second, y = qyx.top().second;
            updatez(x), updatey(z), updatex(y);
        }
    }

    printf("%lld", ans);
    return 0;
}

CF802O April Fools' Problem

\(n\) 道题,第 \(i\) 天可以花费 \(a_i\) 准备一道题、花费 \(b_i\) 打印一道题。

每天最多准备一道、最多打印一道,准备的题可以留到以后打印。

求准备并打印 \(k\) 道题的最小花费。

\(n \leq 5 \times 10^5\)

先用 wqs 二分去掉恰好 \(k\) 题的限制。考虑在每次打印的时候选取准备花费最少且准备+打印总花费为非正数的题目进行打印,但是这样会使得后面一个打印花费更优的位置无法匹配,于是引入反悔操作。

用一个小根堆维护之前的所有决策,每次取出价格最低的决策,将当前打印的花费加上决策花费后,如果总花费为非正数,则先选择该决策,并将 \(-b_i\) 放入堆中,这样就可以反悔了。时间复杂度 \(O(n \log n \log V)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7;

ll a[N], b[N];

int n, k;

inline pair<ll, int> check(ll lambda) {
    ll ans = 0;
    int cnt = 0;
    priority_queue<pair<ll, int> > q;
    
    for (int i = 1; i <= n; ++i)
        b[i] -= lambda;
    
    for (int i = 1; i <= n; ++i) {
        q.emplace(-a[i], 1);
        
        if (b[i] + -q.top().first < 0) {
            ans += b[i] - q.top().first, cnt += q.top().second;
            q.pop(), q.emplace(b[i], 0);
        }
    }
    
    for (int i = 1; i <= n; ++i)
        b[i] += lambda;
    
    return make_pair(ans + lambda * cnt, cnt);
}

signed main() {
    scanf("%d%d", &n, &k);
    
    for (int i = 1; i <= n; ++i)
        scanf("%lld", a + i);
    
    for (int i = 1; i <= n; ++i)
        scanf("%lld", b + i);
    
    ll l = -2e9, r = 2e9, lim = -2e9;
    
    while (l <= r) {
        ll mid = (l + r) >> 1;
        
        if (check(mid).second >= k)
            lim = mid, r = mid - 1;
        else
            l = mid + 1;
    }
    
    printf("%lld", check(lim).first);
    return 0;
}

CF280D k-Maximum Subsequence Sum

给出一个长度为 \(n\) 的数列, \(m\) 次操作,每次有两种:

  • 修改某个位置的值。
  • 询问区间 \([l,r]\) 里选出至多 \(k\) 个不相交的子段和的最大值。

\(n, m \leq 10^5\)\(k \leq 20\)

考虑用线段树维护反悔贪心。处理一个区间询问时贪心选最大子段和,再将其全部取相反数,这样下一次算到时就算反悔。

时间复杂度 \(O(nk \log n)\)

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 7;

int a[N];

int n, m;

namespace SMT {
struct Node {
    int l, r, sum,
        lmxsum, lmxpos, lmnsum, lmnpos,
        rmxsum, rmxpos, rmnsum, rmnpos,
        mxans, lmxanspos, rmxanspos,
        mnans, lmnanspos, rmnanspos;

    inline Node(int x = 0, int k = 0) {
        sum = k, l = r = x;
        mxans = lmxsum = rmxsum = k, rmxpos = lmxpos = lmxanspos = rmxanspos = x;
        mnans = lmnsum = rmnsum = k, rmnpos = lmnpos = lmnanspos = rmnanspos = x;
    }

    inline void opposite() {
        sum = -sum, mxans = -mxans, mnans = -mnans;
        lmxsum = -lmxsum, lmnsum = -lmnsum, rmxsum = -rmxsum, rmnsum = -rmnsum;
        swap(mxans, mnans), swap(lmxanspos, lmnanspos), swap(rmxanspos, rmnanspos);
        swap(lmxsum, lmnsum), swap(rmxsum, rmnsum), swap(rmxpos, rmnpos), swap(lmxpos, lmnpos);
    }

    inline friend Node operator + (Node a, Node b) {
        Node c;
        c.sum = a.sum + b.sum, c.l = a.l, c.r = b.r;

        if (a.sum + b.lmxsum > a.lmxsum)
            c.lmxsum = a.sum + b.lmxsum, c.rmxpos = b.rmxpos;
        else
            c.lmxsum = a.lmxsum, c.rmxpos = a.rmxpos;

        if (a.sum + b.lmnsum < a.lmnsum)
            c.lmnsum = a.sum + b.lmnsum, c.rmnpos = b.rmnpos;
        else
            c.lmnsum = a.lmnsum, c.rmnpos = a.rmnpos;

        if (b.sum + a.rmxsum > b.rmxsum)
            c.rmxsum = b.sum + a.rmxsum, c.lmxpos = a.lmxpos;
        else
            c.rmxsum = b.rmxsum, c.lmxpos = b.lmxpos;

        if (b.sum + a.rmnsum < b.rmnsum)
            c.rmnsum = b.sum + a.rmnsum, c.lmnpos = a.lmnpos;
        else
            c.rmnsum = b.rmnsum, c.lmnpos = b.lmnpos;

        c.mxans = max(max(c.lmxsum, c.rmxsum), max(a.rmxsum + b.lmxsum, max(a.mxans, b.mxans)));

        if (c.mxans == c.lmxsum)
            c.lmxanspos = c.l, c.rmxanspos = c.rmxpos;
        else if (c.mxans == c.rmxsum)
            c.lmxanspos = c.lmxpos, c.rmxanspos = c.r;
        else if (c.mxans == a.rmxsum + b.lmxsum)
            c.lmxanspos = a.lmxpos, c.rmxanspos = b.rmxpos;
        else if (c.mxans == a.mxans)
            c.lmxanspos = a.lmxanspos, c.rmxanspos = a.rmxanspos;
        else if (c.mxans == b.mxans)
            c.lmxanspos = b.lmxanspos, c.rmxanspos = b.rmxanspos;

        c.mnans = min(min(c.lmnsum, c.rmnsum), min(a.rmnsum + b.lmnsum, min(a.mnans, b.mnans)));

        if (c.mnans == c.lmnsum)
            c.lmnanspos = c.l, c.rmnanspos = c.rmnpos;
        else if (c.mnans == c.rmnsum)
            c.lmnanspos = c.lmnpos, c.rmnanspos = c.r;
        else if (c.mnans == a.rmnsum + b.lmnsum)
            c.lmnanspos = a.lmnpos, c.rmnanspos = b.rmnpos;
        else if (c.mnans == a.mnans)
            c.lmnanspos = a.lmnanspos, c.rmnanspos = a.rmnanspos;
        else if (c.mnans == b.mnans)
            c.lmnanspos = b.lmnanspos, c.rmnanspos = b.rmnanspos;

        return c;
    }
} s[N << 2];

int tag[N << 2];

inline int ls(int x) {
    return x << 1;
}

inline int rs(int x) {
    return x << 1 | 1;
}

inline void pushup(int x) {
    s[x] = s[ls(x)] + s[rs(x)];
}

inline void spread(int x) {
    s[x].opposite(), tag[x] ^= 1;
}

inline void pushdown(int x) {
    if (tag[x])
        spread(ls(x)), spread(rs(x)), tag[x] = 0;
}

void build(int x, int l, int r) {
    s[x].l = l, s[x].r = r;

    if (l == r) {
        s[x] = Node(l, a[l]);
        return;
    }

    int mid = (l + r) >> 1;
    build(ls(x), l, mid), build(rs(x), mid + 1, r);
    pushup(x);
}

void update(int x, int nl, int nr, int pos, int k) {
    if (nl == nr) {
        s[x] = Node(pos, k);
        return;
    }

    pushdown(x);
    int mid = (nl + nr) >> 1;

    if (pos <= mid)
        update(ls(x), nl, mid, pos, k);
    else
        update(rs(x), mid + 1, nr, pos, k);

    pushup(x);
}

void modify(int x, int nl, int nr, int l, int r) {
    if (l <= nl && nr <= r) {
        spread(x);
        return;
    }

    pushdown(x);
    int mid = (nl + nr) >> 1;

    if (l <= mid)
        modify(ls(x), nl, mid, l, r);

    if (r > mid)
        modify(rs(x), mid + 1, nr, l, r);

    pushup(x);
}

Node query(int x, int nl, int nr, int l, int r) {
    if (l <= nl && nr <= r)
        return s[x];

    pushdown(x);
    int mid = (nl + nr) >> 1;

    if (r <= mid)
        return query(ls(x), nl, mid, l, r);
    else if (l > mid)
        return query(rs(x), mid + 1, nr, l, r);
    else
        return query(ls(x), nl, mid, l, r) + query(rs(x), mid + 1, nr, l, r);
}
} // namespace SMT

inline int query(int d, int sum, int l, int r) {
    if (!d)
        return 0;

    auto res = SMT::query(1, 1, n, l, r);
    SMT::modify(1, 1, n, res.lmxanspos, res.rmxanspos);
    int ans = max(sum + res.mxans, query(d - 1, sum + res.mxans, l, r));
    SMT::modify(1, 1, n, res.lmxanspos, res.rmxanspos);
    return ans;
}

signed main() {
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a + i);

    SMT::build(1, 1, n);
    scanf("%d", &m);

    while (m--) {
        int op;
        scanf("%d", &op);

        if (op) {
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", query(k, 0, l, r));
        } else {
            int x, k;
            scanf("%d%d", &x, &k);
            SMT::update(1, 1, n, x, k);
        }
    }

    return 0;
}

CF2063D Game With Triangles

平面上有 \(n + m\) 个点 \((a_{1 \sim n}, 0)\)\((b_{1 \sim m}, 2)\) 。每次可以选择三个点组成三角形并删去,分数为其面积。

求最多操作次数为 \(k_{\max}\) ,并对于操作 \(1 \sim k_{\max}\) 次求出最大得分。

\(n, m \leq 2 \times 10^5\)

显然有 \(k_{\max} = \min \{ n, m, \frac{n + m}{3} \}\) ,面积即为纵坐标相同点的横坐标差。

考虑反悔贪心,则每次可以选择:

  • 选一个 \(a\) 为边的三角形。
  • 选一个 \(b\) 为边的三角形。
  • 撤销一个 \(a\) 为边的三角形,选两个 \(b\) 为边的三角形。
  • 撤销一个 \(b\) 为边的三角形,选两个 \(a\) 为边的三角形。

不难发现为了最大化面积,固定顶点是 \(a\) 还是 \(b\) 后贪心选取另一边最左和最右的两个点最优。于是维护堆存储两端线段长度即可做到 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;

int a[N], b[N];

int n, m;

signed main() {
    int T;
    scanf("%d", &T);

    while (T--) {
        scanf("%d%d", &n, &m);

        for (int i = 1; i <= n; ++i)
            scanf("%d", a + i);

        for (int i = 1; i <= m; ++i)
            scanf("%d", b + i);

        sort(a + 1, a + n + 1), sort(b + 1, b + m + 1);
        int kmax = min(min(n, m), (n + m) / 3);
        printf("%d\n", kmax);
        priority_queue<int> qa1, qa2, qb1, qb2;

        for (int i = 1; i <= n / 2; ++i)
            qa1.emplace(a[n - i + 1] - a[i]);

        for (int i = 1; i <= m / 2; ++i)
            qb1.emplace(b[m - i + 1] - b[i]);

        ll ans = 0;

        for (int i = 1, n1 = 0, n2 = 0, m1 = 0, m2 = 0; i <= kmax; ++i) {
            ll res1 = -inf, res2 = -inf, res3 = -inf, res4 = -inf;

            if (2 * (n2 + 1) + n1 <= n && 2 * m2 + (m1 + 1) <= m)
                res1 = qa1.top();

            if (2 * (m2 + 1) + m1 <= m && 2 * n2 + (n1 + 1) <= n)
                res2 = qb1.top();

            if (n2 && m1 && 2 * (n2 - 1) + (n1 + 2) <= n && 2 * (m2 + 2) + (m1 - 1) <= m) {
                res3 = qa2.top() + qb1.top();
                int tmp = qb1.top();
                qb1.pop(), res3 += qb1.top(), qb1.emplace(tmp);
            }

            if (m2 && n1 && 2 * (m2 - 1) + (m1 + 2) <= m && 2 * (n2 + 2) + (n1 - 1) <= n) {
                res4 = qb2.top() + qa1.top();
                int tmp = qa1.top();
                qa1.pop(), res4 += qa1.top(), qa1.emplace(tmp);
            }

            ll res = max(max(res1, res2), max(res3, res4));
            printf("%lld ", ans += res);

            if (res1 == res)
                ++n2, ++m1, qa2.emplace(-qa1.top()), qa1.pop();
            else if (res2 == res)
                ++m2, ++n1, qb2.emplace(-qb1.top()), qb1.pop();
            else if (res3 == res) {
                --n2, n1 += 2, m2 += 2, --m1;
                qa1.emplace(-qa2.top()), qa2.pop();
                qb2.emplace(-qb1.top()), qb1.pop();
                qb2.emplace(-qb1.top()), qb1.pop();
            } else {
                --m2, m1 += 2, n2 += 2, --n1;
                qb1.emplace(-qb2.top()), qb2.pop();
                qa2.emplace(-qa1.top()), qa1.pop();
                qa2.emplace(-qa1.top()), qa1.pop();
            }
        }

        puts("");
    }

    return 0;
}

QOJ9845. Reverse LIS

给定一个 01 串,其被分为 \(n\) 段,第 \(i\) 段由 \(p_i\)\(0\)\(1\) 组成。

你可以对其进行不超过 \(k\) 次翻转子串操作,最大化操作后的串的最长不下降子序列的长度。

\(n \leq 2 \times 10^5\)

首先可以发现翻转的子串一定是整段,这可以用调整法证明。

由于只有 \(0\)\(1\) ,因此最长不下降子序列一定存在一个分界点 \(p\) ,其形如 \(\leq p\)\(0\) 拼上 \(> p\)\(1\)

考虑将 \(0\) 的权值记为 \(1\)\(1\) 的权值记为 \(-1\) ,权值的前缀和记为 \(s_i\)\(1\) 的数量记为 \(cnt\) ,则固定分界点 \(p\) 后最长不下降子序列的长度即为 \(cnt + s_p\) ,问题转化为区间翻转后最大化最大前缀和。

可以发现每次翻转的区间不交,这可以用调整法证明,问题转化为选出一段前缀和 \(\leq k\) 段两两区间(将他们翻转到前缀),最大化选出的权值和。

考虑反悔贪心,先贪心选取最大前缀,然后每次贪心选取最大子段和,并将选取的区间取反以实现反悔操作,用线段树维护这个过程即可做到 \(O(n \log n)\)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e5 + 7;

ll s[N], ans[N];
int a[N];

int n, q;

namespace SMT {
struct Node {
    ll sum, mxans, mnans, lmxsum, lmnsum, rmxsum, rmnsum;
    int l, r, lmxpos, lmnpos, rmxpos, rmnpos, lmxanspos, rmxanspos, lmnanspos, rmnanspos;

    inline Node(int x = 0, int k = 0) {
        sum = k, l = r = x;
        mxans = lmxsum = rmxsum = k, rmxpos = lmxpos = lmxanspos = rmxanspos = x;
        mnans = lmnsum = rmnsum = k, rmnpos = lmnpos = lmnanspos = rmnanspos = x;
    }

    inline void opposite() {
        sum = -sum, mxans = -mxans, mnans = -mnans;
        lmxsum = -lmxsum, lmnsum = -lmnsum, rmxsum = -rmxsum, rmnsum = -rmnsum;
        swap(mxans, mnans), swap(lmxanspos, lmnanspos), swap(rmxanspos, rmnanspos);
        swap(lmxsum, lmnsum), swap(rmxsum, rmnsum), swap(rmxpos, rmnpos), swap(lmxpos, lmnpos);
    }

    inline friend Node operator + (Node a, Node b) {
        Node c;
        c.sum = a.sum + b.sum, c.l = a.l, c.r = b.r;

        if (a.sum + b.lmxsum > a.lmxsum)
            c.lmxsum = a.sum + b.lmxsum, c.rmxpos = b.rmxpos;
        else
            c.lmxsum = a.lmxsum, c.rmxpos = a.rmxpos;

        if (a.sum + b.lmnsum < a.lmnsum)
            c.lmnsum = a.sum + b.lmnsum, c.rmnpos = b.rmnpos;
        else
            c.lmnsum = a.lmnsum, c.rmnpos = a.rmnpos;

        if (b.sum + a.rmxsum > b.rmxsum)
            c.rmxsum = b.sum + a.rmxsum, c.lmxpos = a.lmxpos;
        else
            c.rmxsum = b.rmxsum, c.lmxpos = b.lmxpos;

        if (b.sum + a.rmnsum < b.rmnsum)
            c.rmnsum = b.sum + a.rmnsum, c.lmnpos = a.lmnpos;
        else
            c.rmnsum = b.rmnsum, c.lmnpos = b.lmnpos;

        c.mxans = max(max(c.lmxsum, c.rmxsum), max(a.rmxsum + b.lmxsum, max(a.mxans, b.mxans)));

        if (c.mxans == c.lmxsum)
            c.lmxanspos = c.l, c.rmxanspos = c.rmxpos;
        else if (c.mxans == c.rmxsum)
            c.lmxanspos = c.lmxpos, c.rmxanspos = c.r;
        else if (c.mxans == a.rmxsum + b.lmxsum)
            c.lmxanspos = a.lmxpos, c.rmxanspos = b.rmxpos;
        else if (c.mxans == a.mxans)
            c.lmxanspos = a.lmxanspos, c.rmxanspos = a.rmxanspos;
        else if (c.mxans == b.mxans)
            c.lmxanspos = b.lmxanspos, c.rmxanspos = b.rmxanspos;

        c.mnans = min(min(c.lmnsum, c.rmnsum), min(a.rmnsum + b.lmnsum, min(a.mnans, b.mnans)));

        if (c.mnans == c.lmnsum)
            c.lmnanspos = c.l, c.rmnanspos = c.rmnpos;
        else if (c.mnans == c.rmnsum)
            c.lmnanspos = c.lmnpos, c.rmnanspos = c.r;
        else if (c.mnans == a.rmnsum + b.lmnsum)
            c.lmnanspos = a.lmnpos, c.rmnanspos = b.rmnpos;
        else if (c.mnans == a.mnans)
            c.lmnanspos = a.lmnanspos, c.rmnanspos = a.rmnanspos;
        else if (c.mnans == b.mnans)
            c.lmnanspos = b.lmnanspos, c.rmnanspos = b.rmnanspos;

        return c;
    }
} nd[N << 2];

int tag[N << 2];

inline int ls(int x) {
    return x << 1;
}

inline int rs(int x) {
    return x << 1 | 1;
}

inline void pushup(int x) {
    nd[x] = nd[ls(x)] + nd[rs(x)];
}

inline void spread(int x) {
    nd[x].opposite(), tag[x] ^= 1;
}

inline void pushdown(int x) {
    if (tag[x])
        spread(ls(x)), spread(rs(x)), tag[x] = 0;
}

void build(int x, int l, int r) {
    nd[x].l = l, nd[x].r = r;

    if (l == r) {
        nd[x] = Node(l, a[l]);
        return;
    }

    int mid = (l + r) >> 1;
    build(ls(x), l, mid), build(rs(x), mid + 1, r);
    pushup(x);
}

void update(int x, int nl, int nr, int l, int r) {
    if (l <= nl && nr <= r) {
        spread(x);
        return;
    }

    pushdown(x);
    int mid = (nl + nr) >> 1;

    if (l <= mid)
        update(ls(x), nl, mid, l, r);

    if (r > mid)
        update(rs(x), mid + 1, nr, l, r);

    pushup(x);
}
} // namespace SMT

signed main() {
    scanf("%d", &n);
    ll sum = 0;

    for (int i = 1; i <= n; ++i) {
        int c, p;
        scanf("%d%d", &c, &p);

        if (c)
            a[i] = -p, ans[0] += p;
        else
            a[i] = p;

        s[i] = s[i - 1] + a[i], sum += p;
    }

    SMT::build(1, 1, n);
    int p = max_element(s, s + n + 1) - s;
    ans[0] += s[p];

    if (p)
        SMT::update(1, 1, n, 1, p);

    p = 0;

    while (SMT::nd[1].mxans > 0) {
        ++p, ans[p] = ans[p - 1] + SMT::nd[1].mxans;
        SMT::update(1, 1, n, SMT::nd[1].lmxanspos, SMT::nd[1].rmxanspos);
    }
    
    scanf("%d", &q);

    while (q--) {
        int k;
        scanf("%d", &k);
        printf("%lld\n", ans[min(k, p)]);
    }

    return 0;
}
posted @ 2025-02-13 13:06  wshcl  阅读(296)  评论(0)    收藏  举报