【codeforces】1398E. Two Types of Spells——在线与离线两种做法——线段树、动态开点权值线段树、时间分治

题面链接

大致题意

有两种法术,法术类型为 0 0 0 的法术能造成伤害,法术类型为 1 1 1 的法术不仅能造成伤害,还能使下一次法术的伤害翻倍。

现在动态的获得/删除法术,请问对于每一次操作之后,你能造成的最大伤害是多少

分析

题意分析

对于一个动态的数列,其中有两种值,紧贴着 B B B 类型值之后的值翻倍,求问每个时刻整个数列的最大值。
考虑最优的情况,让每一个 B B B 类型的值之后的值是整个序列中最大的那 k k k 个值,即可。但是无论如何组合,第一个 B B B 类型的值必然不能翻倍,也就是一定存在一个 B B B 类型的值不能翻倍,那我们固定取最小的那个 B B B 类型的值,则题意可以转为如下

大致的转换一下题意

给出一个序列,起始的时候序列为空
每次将一个数值加入到序列中,或者从序列中删除一个数字
每次操作后,给出两个值: s , k s, k s,k,请从序列中选出 k k k 个值,在这 k k k 个值不包含 s s s 前提的和的最大值是多少

方法

首先用一个 set 可以记录当前所有的 B B B 类型的值,则 set 的第一个值便是最小的不可选的值 s s s
而后求前缀和,可以将当前时间的所有值之和求出,则答案只需要求出前 k k k 大且不包含 s s s 的和即可

在线

求出前 k k k 大不难想到主席树,而主席树是可持久化线段树,在本题的在线做法中,并不需要保留历史版本,所以只需要普通的权值线段树即可。

建立一棵下标范围为 [ 1 , 1 e 9 ] [1, 1e9] [1,1e9] 的权值线段树,表示每个值出现的次数(虽然在本题中每个值仅出现一次),线段树上的每个节点维护了它的子树权值和与子树价值的加权和。

同时权值线段树也提供了求算 s s s 在整个序列中的位置,如果 s s s 在前 k k k 大中,则需要求算前 k + 1 k + 1 k+1 的解,并减去 s s s。如果不在则直接求解即可。

考虑到数值范围为 [ 1 , 1 e 9 ] [1, 1e9] [1,1e9] 所以采用动态开点的线段树即可

AC code

#include 

using namespace std;

#define int long long

const int MAXN = 2e5 + 100;

struct SegTree {
    int data[MAXN * 40];
    int cnt[MAXN * 40];
    int lSon[MAXN * 40], rSon[MAXN * 40];
    int tot = 1;

    inline void reset() {
        tot = 0;
        next();
    }

    inline int next() {
        data[tot] = 0;
        cnt[tot] = 0;
        lSon[tot] = 0;
        rSon[tot] = 0;
        return tot++;
    }

    inline int lson(int k) {
        if (!lSon[k]) lSon[k] = next();
        return lSon[k];
    }

    inline int rson(int k) {
        if (!rSon[k]) rSon[k] = next();
        return rSon[k];
    }

    inline void up(int k) {
        data[k] = (lSon[k] ? data[lSon[k]] : 0) + (rSon[k] ? data[rSon[k]] : 0);
        cnt[k] = (lSon[k] ? cnt[lSon[k]] : 0) + (rSon[k] ? cnt[rSon[k]] : 0);
    }

    void insert(int value, int k = 0, int l = 1, int r = 1e9) {
        if (l == r) {
            data[k] = value;
            cnt[k]++;
            return;
        }
        int mid = (l + r) >> 1;
        if (value <= mid)
            insert(value, lson(k), l, mid);
        else
            insert(value, rson(k), mid + 1, r);
        up(k);
    }

    /**
     * 删除一个值,前提这个值得存在
     * @param value
     * @param k
     * @param l
     * @param r
     */
    void erase(int value, int k = 0, int l = 1, int r = 1e9) {
        if (l == r) {
            cnt[k]--;
            data[k] = 0;
            return;
        }
        int mid = (l + r) >> 1;
        if (value <= mid)
            erase(value, lSon[k], l, mid);
        else
            erase(value, rSon[k], mid + 1, r);
        up(k);
    }

    /**
     * 查找一个值是第几大[0, n),前提这个值得存在
     * @param value
     * @param k
     * @param l
     * @param r
     */
    int find(int value, int k = 0, int l = 1, int r = 1e9) {
        if (l == r)
            return 0;
        int mid = (l + r) >> 1;
        if (value <= mid)
            return (rSon[k] ? cnt[rSon[k]] : 0) + find(value, lSon[k], l, mid);
        else
            return find(value, rSon[k], mid + 1, r);
    }

    int sum(int num, int k = 0) {
        if (num == 0) return 0;
        if (cnt[k] == num) return data[k];
        int rCnt = min(num, rSon[k] ? cnt[rSon[k]] : 0), lCnt = num - rCnt;
        return ((lSon[k] && lCnt) ? sum(lCnt, lSon[k]) : 0) + ((rSon[k] && rCnt) ? sum(rCnt, rSon[k]) : 0);
    }
} segTree;

int solve(int k, int mi) {
    int pos = segTree.find(mi);
    if (pos < k)
        return segTree.sum(k + 1) - mi;
    return segTree.sum(k);
}

void solve() {
    int n;
    cin >> n;
    segTree.reset();
    set<int> pool;
    for (int i = 0; i < n; ++i) {
        int op, value;
        cin >> op >> value;
        if (value > 0)
            segTree.insert(value);
        else
            segTree.erase(-value);
        if (op == 1) {
            if (value > 0)
                pool.insert(value);
            else
                pool.erase(-value);
        }
        if (segTree.cnt[0] == 0) {
            cout << 0 << endl;
            continue;
        }
        if (pool.size() == segTree.cnt[0]) {
            cout << segTree.data[0] * 2 - (*pool.begin()) << endl;
            continue;
        }
        if (pool.size())
            cout << solve(pool.size(), *pool.begin()) + segTree.data[0] << endl;
        else
            cout << segTree.data[0] << endl;

    }
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed localTestCount = 1, localReadPos = cin.tellg();
    char localTryReadChar;
    do {
        if (localTestCount > 20)
            throw runtime_error("Check the stdin!!!");
        auto startClockForDebug = clock();
        solve();
        auto endClockForDebug = clock();
        cout << "Test " << localTestCount << " successful" << endl;
        cerr << "Test " << localTestCount++ << " Run Time: "
             << double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
             cin.putback(localTryReadChar));
#else
    solve();
#endif
    return 0;
}

离线

考虑时间分治,对于所有出现的值,我们可以记录它出现的时间区间,然后将其保存在线段树上。

考虑每次需要取出前 k k k 大,所以我们进行如下处理:

线段树的每个节点保存了 4 4 4个值:mimadatalazy
分别指代:mi:此节点下所有子树中还需要 k k k 最小的值。ma:此节点下所有子树中还需要 k k k 最大的值。data:此节点已经得到的值的和,lazy:懒惰标记——有多少个值未被下推至叶子节点。
其中 mi 表示这个节点还能否保存值,ma 表示还能否有值经过这个节点

对于 s s s,则计算出每个时间内哪个值不可以被选,则不让这个值在这些时间点被加入到线段树中即可。

AC code

#include 

using namespace std;

#define int long long

const int MAXN = 2e5 + 100;

struct SegTree {
    int mi[MAXN << 2], ma[MAXN << 2];
    int data[MAXN << 2], lazy[MAXN << 2];

    inline int lson(int k) { return k << 1; }

    inline int rson(int k) { return (k << 1) | 1; }

    inline void up(int k) {
        mi[k] = min(mi[lson(k)], mi[rson(k)]);
        ma[k] = max(ma[lson(k)], ma[rson(k)]);
    }

    inline void push(int k, int l, int r) {
        if (l == r) return;
        if (lazy[k] == 0) return;

        mi[lson(k)] -= lazy[k];
        mi[rson(k)] -= lazy[k];

        ma[lson(k)] -= lazy[k];
        ma[rson(k)] -= lazy[k];

        data[lson(k)] += data[k];
        data[rson(k)] += data[k];

        lazy[lson(k)] += lazy[k];
        lazy[rson(k)] += lazy[k];

        data[k] = 0;
        lazy[k] = 0;
    }

    void build(int k, int l, int r, const vector<int> &a) {
        data[k] = 0;
        lazy[k] = 0;
        if (l == r) {
            mi[k] = ma[k] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(lson(k), l, mid, a);
        build(rson(k), mid + 1, r, a);
        up(k);
    }

    void update(int value, int x, int y, int k, int l, int r) {
        if (ma[k] <= 0) return;
        if (x == l && y == r && mi[k]) {
            lazy[k]++;
            data[k] += value;
            ma[k]--;
            mi[k]--;
            return;
        }
        push(k, l, r);
        int mid = (l + r) >> 1;
        if (y <= mid)
            update(value, x, y, lson(k), l, mid);
        else if (x > mid)
            update(value, x, y, rson(k), mid + 1, r);
        else {
            update(value, x, mid, lson(k), l, mid);
            update(value, mid + 1, y, rson(k), mid + 1, r);
        }
        up(k);
    }

    void build(int k, int l, int r) {
        if (l == r) return;
        push(k, l, r);
        int mid = (l + r) >> 1;
        build(lson(k), l, mid);
        build(rson(k), mid + 1, r);
    }

    void print(int k, int l, int r, const vector<int> &tot) {
        if (l == r) {
            cout << data[k] + tot[l] << endl;
            return;
        }
        int mid = (l + r) >> 1;
        print(lson(k), l, mid, tot);
        print(rson(k), mid + 1, r, tot);
    }
} segTree;

void solve() {
    int n;
    cin >> n;
    map<int, vector<pair<int, int>>> pool;
    set<int> lightning;
    vector<int> cnt(n + 1), tot(n + 1);
    tot[0] = 0;
    for (int i = 1; i <= n; ++i) {
        int op, u;
        cin >> op >> u;
        auto iter = pool.find(abs(u));
        if (iter == pool.end()) {
            pool.insert({u, {}});
            iter = pool.find(u);
        }
        if (u > 0) {
            iter->second.push_back({i, n});
        } else {
            iter->second.back().second = i - 1;
        }
        if (op == 1) {
            if (u > 0) lightning.insert(u);
            else lightning.erase(-u);
        }
        if (!lightning.empty()) {
            iter = pool.find(*lightning.begin());
            iter->second.back().second = i - 1;
            iter->second.push_back({i + 1, n});
        }
        cnt[i] = lightning.size();
        tot[i] = tot[i - 1] + u;
    }
    segTree.build(1, 1, n, cnt);
    auto iter = pool.rbegin();
    while (iter != pool.rend()) {
        for (auto item : iter->second)
            if (item.first <= item.second) segTree.update(iter->first, item.first, item.second, 1, 1, n);
        iter++;
    }
    segTree.build(1, 1, n);
    segTree.print(1, 1, n, tot);
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
#ifdef ACM_LOCAL
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    signed localTestCount = 1, localReadPos = cin.tellg();
    char localTryReadChar;
    do {
        if (localTestCount > 20)
            throw runtime_error("Check the stdin!!!");
        auto startClockForDebug = clock();
        solve();
        auto endClockForDebug = clock();
        cout << "Test " << localTestCount << " successful" << endl;
        cerr << "Test " << localTestCount++ << " Run Time: "
             << double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
        cout << "--------------------------------------------------" << endl;
    } while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
             cin.putback(localTryReadChar));
#else
    solve();
#endif
    return 0;
}

对比

在线

在线
考虑到主席树,所以就开了一大堆的空间

离线

离线

对比

对比而言,两种方法的效率近乎相同

什么,你问我为什么我的 codeforces 和你的不太一样(Codeforces 伪客户端)

你可能感兴趣的:(ACM,#,数据结构)