「Fortuna OJ」Mar 7th – Group A 解题报告

A – 高维宇宙

裸的二分图匹配,匹配数除以二即可。

// A.cpp
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 100;
int head[MAX_N], n, current, matching[MAX_N], dfn[MAX_N], arr[MAX_N];
bool isPrime[2020];
struct edge
{
    int to, nxt;
} edges[(MAX_N * (MAX_N - 1)) >> 1];
void addpath(int src, int dst)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    head[src] = current++;
}
bool dfs(int u, int org)
{
    for (int i = head[u]; i != -1; i = edges[i].nxt)
    {
        int to = edges[i].to;
        if (dfn[to] != org)
        {
            dfn[to] = org;
            if ((!matching[to]) || dfs(matching[to], org))
            {
                matching[to] = u;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    freopen("prime.in", "r", stdin);
    freopen("prime.out", "w", stdout);
    memset(head, -1, sizeof(head)), memset(isPrime, true, sizeof(isPrime));
    int ans = 0;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &arr[i]);
    for (int i = 2; i < 2020; i++)
        if (isPrime[i])
            for (int j = 2; i * j < 2020; j++)
                isPrime[j * i] = false;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if (isPrime[arr[i] + arr[j]])
                addpath(i, j);
    for (int i = 1; i <= n; i++)
        if (dfs(i, i))
            ans++;
    printf("%d", ans / 2);
    return 0;
}

B – 排队

这道题我在考场上 A 掉了,方法跟题解不太一样(欢迎来 Hack),当然复杂度可能会高一点。

我们考虑魔改 DFS 序,在进入节点之前先进行排序,排序之后 DFS 完了之后再进行标记。可以发现,这就是我们在不删除节点的情况下的最优放法:

如上图所示,这就是我们在不搞删除的情况下放置的最佳顺序:4、7、3、2、6、5、1。我们定义这种顺序叫做乱搞序。我们来讨论使用一种乱搞的线段树(以上面那种顺序做关键字)来搞定这两个操作。

\(OPT = 1\)的情况下:我们先考虑之前没有删除操作的情况,其实很简单,直接往最后一个乱搞序最小的槽位设置为一即可;如果之前有过删除操作的话,那么已有的乱搞序上肯定不是连续的1:一定有个0表示被删除,我们可以考虑在 UPDATE 操作里面判断左右儿子是否有不满足连续的情况(也就是和等于区间长度),如果有那么就从加数里取出一些,如果加数有剩余,那么左儿子直接打标记,然后递归右儿子:因为我们设置 UPDATE 操作的返回值为最后一个乱搞值,之后通过 anti 数组重新转换为节点编号。

int update(int l, int r, int p, int c)
{
    if (c == 0)
        return -1;
    if (l == r)
    {
        tree[p] += c;
        return l;
    }
    int lft = (mid - l + 1) - tree[lson], rig = c - lft;
    int ans = 0;
    if (rig > 0)
    {
        tree[lson] = mid - l + 1, lazy[lson] = 1;
        ans = update(mid + 1, r, rson, rig);
    }
    else
        ans = update(l, mid, lson, c);
    tree[p] = tree[lson] + tree[rson];
    return ans;
}

\(OPT=2\)的情况下也比较简单,我们只要找出该节点到根节点链上的已用节点个数并减去一即可,进行单点修改。

建议详读代码,理解我的乱搞思想。

// B.cpp
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define lson (p << 1)
#define rson ((p << 1) | 1)
#define lowbit(x) (x & -x)
using namespace std;
const int MAX_N = 1e5 + 2000;
int n, t, dfn[MAX_N], st[20][MAX_N], tmpx, tmpy, tree[MAX_N << 2], lazy[MAX_N << 2], dtot, anti[MAX_N];
vector<int> G[MAX_N];
void addpath(int src, int dst) { G[src].push_back(dst); }
void pushdown(int p, int l, int r)
{
    if (lazy[p] && l != r)
    {
        tree[lson] = mid - l + 1, tree[rson] = r - mid;
        lazy[lson] = 1, lazy[rson] = 1;
    }
    lazy[p] = 0;
}
int update(int l, int r, int p, int c)
{
    if (c == 0)
        return -1;
    if (l == r)
    {
        tree[p] += c;
        return l;
    }
    int lft = (mid - l + 1) - tree[lson], rig = c - lft;
    int ans = 0;
    if (rig > 0)
    {
        tree[lson] = mid - l + 1, lazy[lson] = 1;
        ans = update(mid + 1, r, rson, rig);
    }
    else
        ans = update(l, mid, lson, c);
    tree[p] = tree[lson] + tree[rson];
    return ans;
}
void remove(int qx, int l, int r, int p)
{
    if (l == r && l == qx)
    {
        tree[p] = 0, lazy[p] = 0;
        return;
    }
    pushdown(p, l, r);
    if (qx <= mid)
        remove(qx, l, mid, lson);
    else
        remove(qx, mid + 1, r, rson);
    tree[p] = tree[lson] + tree[rson];
}
bool query(int qx, int l, int r, int p)
{
    if (l == r && l == qx)
        return tree[p];
    pushdown(p, l, r);
    if (qx <= mid)
        return query(qx, l, mid, lson);
    else
        return query(qx, mid + 1, r, rson);
}
void dfs(int u)
{
    sort(G[u].begin(), G[u].end());
    int siz = G[u].size();
    for (int i = 0; i < siz; i++)
    {
        int to = G[u][i];
        if (st[0][u] != to)
            st[0][to] = u, dfs(to);
    }
    dfn[u] = ++dtot, anti[dfn[u]] = u;
}
int main()
{
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n - 1; i++)
        scanf("%d%d", &tmpx, &tmpy), addpath(tmpx, tmpy), addpath(tmpy, tmpx);
    dfs(1);
    for (int i = 1; i < 20; i++)
        for (int j = 1; j <= n; j++)
            st[i][j] = st[i - 1][st[i - 1][j]];
    while (t--)
    {
        int opt, x;
        scanf("%d%d", &opt, &x);
        if (opt == 1)
            printf("%d\n", anti[update(1, n, 1, x)]);
        else
        {
            int u = x, ans = 0;
            for (int i = 19; i >= 0; i--)
                if (st[i][u] != 0 && query(dfn[st[i][u]], 1, n, 1) == true)
                    u = st[i][u], ans += 1 << i;
            printf("%d\n", ans);
            remove(dfn[u], 1, n, 1);
        }
    }
    return 0;
}

C – 心理学概论

我考场上以为是费用流,然后打凉掉了。

可以考虑先对序列进行排序(按第一、二和三关键字),然后再进行分段:二分出两个位置\(mid,mid1\),然后分成三段:\( [1,mid],[mid+1,mid1],[mid1+1,n] \)

之后统计即可。

// C.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 1e5 + 1000;
struct cas
{
    ll x, y, z;
    bool operator<(const cas &cs) const { return x < cs.x || (cs.x == x && y < cs.y) || (y == cs.y && x == cs.x && z < cs.z); }
} cases[MAX_N];
ll n, ans1, ans2, ans3, ans;
int main()
{
    freopen("psy.in", "r", stdin);
    freopen("psy.out", "w", stdout);
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld%lld%lld", &cases[i].x, &cases[i].y, &cases[i].z);
        ans1 = max(ans1, cases[i].x), ans2 = max(ans2, cases[i].y), ans3 = max(ans3, cases[i].z);
    }
    ans = min(ans1, min(ans2, ans3));
    sort(cases + 1, cases + 1 + n);
    ll l = 0, r = n, l1 = 0, r1 = n;
    while (l < r)
    {
        ll mid = (l + r) >> 1;
        l1 = 0, r1 = n;
        bool flag = true;
        while (l1 < r1)
        {
            ll mid1 = (l1 + r1) >> 1;
            if (mid1 == mid)
                if (mid1 + 1 <= r)
                    mid1++;
                else if (mid1 - 1 >= l)
                    mid1--;
                else
                {
                    flag = false;
                    break;
                }
            ll k = 0;
            for (int i = 1; i <= n; i++)
            {
                if (cases[i].x <= cases[mid].x)
                    continue;
                if (cases[i].y <= cases[mid1].y)
                    continue;
                if (cases[i].z > k)
                    k = cases[i].z;
            }
            ans1 = cases[mid].x + cases[mid1].y + k;
            if (ans1 < ans)
                ans = ans1, flag = true, r1 = mid1;
            else
                l1 = mid1 + 1;
        }
        if (flag)
            r = mid;
        else
            l = mid + 1;
    }
    ll k = 0;
    for (int i = 1; i <= n; i++)
    {
        if (cases[i].x <= cases[l].x)
            continue;
        if (cases[i].y <= cases[l1].y)
            continue;
        if (cases[i].z > k)
            k = cases[i].z;
    }
    ans = min(ans, cases[l].x + cases[l1].y + k);
    printf("%lld", ans);
    return 0;
}

P3924:康娜的线段树题解

思路

这道题很好,很有趣。

如果您是神仙的话呢,可以考虑直接线段是卡掉本题(有人试过了),但是我这个菜鸡不行,所以写了题解的\(O(1)\)询问解法。

首先,树形 DP 的方程为:

\[ dp[u] = \frac{1}{2} * (dp[lson]+dp[rson]) \]

我们观察叶子节点对答案的贡献,发现为该条链上的权值和除掉\(2^{dep-1}\),也就是与深度有关。那么,为了简化答案,我们可以把这个写作:

\[ \frac{1}{2^{dep-1}} = 2^{1-dep} = \frac { 2^{maxDep – dep} }{ 2^{maxDep-1} } \]

就变成了乘法形式。我们在讨论询问的问题。在区间\([l,r]\)之间加上\(x\),链对答案的贡献是:

\[ x · \sum_{i = 1}^{dep} \frac{1}{2^{i-1}} \]

所以,维护一个前缀和,往答案上面加上前缀和段和这个贡献的积即可获得答案。(记得用 gcd 约分)

代码

// P3924.cpp
#include <bits/stdc++.h>
#define ll long long
#define mid ((l + r) >> 1)
#define lson (p << 1)
#define rson ((p << 1) | 1)
using namespace std;
const int MAX_N = 1e6 + 2000;
ll n, m, qwq, sum[MAX_N << 2], depth[MAX_N << 2], arr[MAX_N], s[MAX_N], max_dep;
inline ll read()
{
    ll s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
void build(ll l, ll r, ll dp, ll p)
{
    if (l == r)
    {
        sum[p] = arr[l], depth[l] = dp, max_dep = max(max_dep, dp);
        return;
    }
    build(l, mid, dp + 1, lson), build(mid + 1, r, dp + 1, rson);
    sum[p] = sum[lson] + sum[rson];
}
ll query(ll l, ll r, ll p, ll dep, ll prefix)
{
    if (l == r)
        return (1 << dep) * (prefix + sum[p]);
    return query(l, mid, lson, dep - 1, prefix + sum[p]) + query(mid + 1, r, rson, dep - 1, prefix + sum[p]);
}
ll gcd(ll a, ll b) { return (b == 0) ? a : gcd(b, a % b); }
int main()
{
    n = read(), m = read(), qwq = read();
    for (int i = 1; i <= n; i++)
        arr[i] = read();
    build(1, n, 1, 1);
    ll ans = query(1, n, 1, max_dep - 1, 0), dominator = 1 << (max_dep - 1);
    ll fact = gcd(dominator, qwq);
    dominator /= fact, qwq /= fact;
    for (int i = 1; i <= n; i++)
        s[i] = s[i - 1] + (((1 << depth[i]) - 1) << (max_dep - depth[i]));
    while (m--)
    {
        ll l = read(), r = read(), w = read();
        ans += (s[r] - s[l - 1]) * w;
        printf("%lld\n", ans / dominator * qwq);
    }
    return 0;
}

「Fortuna OJ」Feb 25th – Group A 解题报告

A – Tourist Problem

非常有趣的一道数学题,我们来分析一下。原讲稿:https://comfortablecom.wordpress.com/2017/10/06/jzoj-b%E7%BB%84-10-6-%E6%80%BB%E7%BB%93/

我们先设一种情况的答案为\(S_x\),显然,可以写成:

\[ S_x = |a_{x_1}-0|+|a_{x_2}-a_{x_1}|+|a_{x_3}-a_{x_2}|+|a_{x_4}-a_{x_3}|+\dots +|a_{x_n}-a_{x_{n-1}}| \]

这一长串的答案其实就是被减数与减数的组合。我们会发现,几乎每一个数都在这个式子中扮演了被减数与减数的角色,除了最后一个元素,即\(a_{x_n}\)。这个元素只充当了一次被减数,仅此而已。

我们考虑\(i\)对答案的贡献。首先,\(i\)作为被减数一共有\(n!\)中可能,其中减数缺了一部分的可能,这一缺失的部分就是\(i\)作为\(a_{x_n}\)的可能数,也就是\((n-1)!\)。之后,考虑在一种序列中\(i\)的贡献。当\(j<i\)时,包括\(0\),一共有\(i\)个数使\(i\)可以在不变号的情况下减去减数;而,当\(i<j\)时,情况就不同了,一共有\(n-i\)种数是的\(i\)在变号的情况下进行对答案的贡献。部分分析结束,总结为一个式子:

\[ a_i(n-1)!i+(-a_i)(n-1)!(n-i) \]

结合上面我所分析的可能方案数和不同情况下的讨论,请读者用心领悟。

而作为减数其实就是换汤不换药了。只要把正负的两种关系变化一下即可,计算式为:

\[ (-a_i)(n-1)!(n-i)+a_i(n-1)!(i-1) \]

注意,此式中最后一项中的\(i-1\)是因为\(0\)无法作为其被减数而造成的。有那么一点点不对称吧。

代码

// A.cpp
#include <bits/stdc++.h>
#define ll unsigned long long
using namespace std;
const int MAX_N = 100200;
int n, arr[MAX_N];
ll gcd(ll a, ll b) { return (b == 0) ? a : gcd(b, a % b); }
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &arr[i]);
    sort(arr + 1, arr + 1 + n);
    ll x = 0;
    for (int i = 1; i <= n; i++)
        x = x + 1LL * arr[i] * (1LL * 4 * i - 1LL * 2 * n - 1);
    ll d = gcd(x, n);
    printf("%lld %lld", x / d, n / d);
    return 0;
}

B – 工作安排 Work

这道题其实在考场上连斜率式子都推完了,但是因为太懒而且不知道怎么隔着\(k\)进行转移所以弃疗,最后边界忘记检查 50 分傻*暴力都没拿到。

这道题的 DP 方程式可通过一眼法得出,其中需要先进行排序:

\[ dp[i] = min\{ f[j-1]+C+(f[i]-f[j])^2 \}, j-1 \in [0,i-k] \]

之后我们来思考如何进行斜率优化。斜率优化是一个非常常用的 DP 优化手段,建议深入了解。我们设\(k\)为我们想要的最优解,那它一定满足:

\[ f[k-1]+C+(f[i]-f[k])^2 < f[j-1]+C+(f[i]-f[j]))^2, j \in [1, i-k+1], j \neq k \\ (f[k-1]-f[j-1])+(f[k]^2-f[j]^2) < 2f[i](f[k]-f[j]) \]

判断时,注意正负号对不等式的影响。

代码

// B.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 2e6 + 20000;
const ll INF = (0x3f3f3f3f3f3f3f3f);
ll f[MAX_N], arr[MAX_N], n, k, c, q[MAX_N << 2];
int head = 1, tail = 0;
ll pow(ll num) { return num * num; }
double check(ll a, ll b)
{
    return ((f[b - 1] + pow(arr[b])) - (f[a - 1] + pow(arr[a]))) / (2.0 * (arr[b] - arr[a]));
}
void insert(int x)
{
    while (head <= tail && arr[q[tail]] == arr[x])
        if (f[x - 1] < f[q[tail] - 1])
            tail--;
        else
            return;
    while (head < tail && check(q[tail - 1], q[tail]) > check(q[tail], x))
        tail--;
    q[++tail] = x;
}
int main()
{
    freopen("work1.in", "r", stdin);
    scanf("%lld%lld%lld", &n, &k, &c);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &arr[i]);
    f[0] = 0;
    sort(arr + 1, arr + 1 + n);
    for (int i = 1; i < k; i++)
        f[i] = INF;
    for (int i = k; i <= n; i++)
    {
        int x = i - k + 1;
        insert(x);
        while (head < tail && check(q[head], q[head + 1]) < arr[i])
            head++;
        f[i] = f[q[head] - 1] + c + pow(arr[q[head]] - arr[i]);
    }
    printf("%lld", f[n]);
    return 0;
}

C – 阿Q的停车场 Park

一道线段树的题目(我改题的时候差点下定决心整晚写个 Treap 的版本来搞,最后发现线段树绰绰有余)。我们维护左边极点、右边极点、最长空位长度和最佳停车点。线段树更新的时候选择左区间、合并区间和右区间进行比较即可。

代码

// C.cpp
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define lson (p << 1)
#define rson ((p << 1) | 1)
using namespace std;
const int MAX_N = 200200, INF = 0x3f3f3f3f;
int n, m, tree_l[MAX_N << 2], tree_r[MAX_N << 2], tree_len[MAX_N << 2], tree_pt[MAX_N << 2], park[(int)1e6 + 2000];
int getFirst(int a, int b, int c, int d)
{
    if (a > 0)
        return a;
    if (b > 0)
        return b;
    if (c > 0)
        return c;
    if (d > 0)
        return d;
    return d;
}
void update(int qx, int l, int r, int p)
{
    if (l == r && l == qx)
    {
        tree_l[p] = l, tree_r[p] = r, tree_len[p] = 0, tree_pt[p] = 0;
        return;
    }
    if (qx <= mid)
        update(qx, l, mid, lson);
    else
        update(qx, mid + 1, r, rson);
    tree_l[p] = getFirst(tree_l[lson], tree_r[lson], tree_l[rson], tree_r[rson]);
    tree_r[p] = getFirst(tree_r[rson], tree_l[rson], tree_r[lson], tree_l[lson]);
    // check three intervals;
    // first;
    tree_len[p] = tree_len[lson], tree_pt[p] = tree_pt[lson];
    if (tree_l[rson] > 0 && tree_r[lson] > 0 && ((tree_l[rson] - tree_r[lson]) >> 1) > tree_len[p])
        tree_len[p] = (tree_l[rson] - tree_r[lson]) >> 1, tree_pt[p] = (tree_r[lson] + tree_l[rson]) >> 1;
    if (tree_len[rson] > tree_len[p])
        tree_len[p] = tree_len[rson], tree_pt[p] = tree_pt[rson];
}
void remove(int qx, int l, int r, int p)
{
    if (l == r && l == qx)
    {
        tree_l[p] = tree_r[p] = tree_len[p] = tree_pt[p] = 0;
        return;
    }
    if (qx <= mid)
        remove(qx, l, mid, lson);
    else
        remove(qx, mid + 1, r, rson);
    tree_l[p] = getFirst(tree_l[lson], tree_r[lson], tree_l[rson], tree_r[rson]);
    tree_r[p] = getFirst(tree_r[rson], tree_l[rson], tree_r[lson], tree_l[lson]);
    // check three intervals;
    // first;
    tree_len[p] = tree_len[lson], tree_pt[p] = tree_pt[lson];
    if (tree_l[rson] > 0 && tree_r[lson] > 0 && ((tree_l[rson] - tree_r[lson]) >> 1) > tree_len[p])
        tree_len[p] = (tree_l[rson] - tree_r[lson]) >> 1, tree_pt[p] = (tree_r[lson] + tree_l[rson]) >> 1;
    if (tree_len[rson] > tree_len[p])
        tree_len[p] = tree_len[rson], tree_pt[p] = tree_pt[rson];
}
int main()
{
    scanf("%d%d", &n, &m);
    while (m--)
    {
        int opt, x;
        scanf("%d%d", &opt, &x);
        if (opt == 1)
        {
            if (tree_l[1] > 0)
            {
                int tmp = tree_len[1], key = tree_pt[1];
                if (tree_l[1] - 1 >= tmp)
                    key = 1, tmp = tree_l[1] - 1;
                if (n - tree_r[1] > tmp)
                    key = n, tmp = n - tree_r[1];
                park[x] = key;
            }
            else
                park[x] = 1;
            printf("%d\n", park[x]), update(park[x], 1, n, 1);
        }
        else
            remove(park[x], 1, n, 1);
    }
    return 0;
}

 

李超树

简述

李超树得名于其发明者——杭州学军中学的李超大神。这个数据结构主要用于解决动态维护(上\下)凸包,是一个非常神仙的东西,主要难点在于处理线段树上的懒惰标记。

算法代码与解释

这个算法有下面几个函数:

  • INTERSECT(k1, k2, b1, b2):求两直线交点的横坐标。
  • UPDATE(k, b, l, r, p):向李超树中加入直线。
  • QUERY(qx, l, r, p):询问当横坐标为\(qx\)时纵坐标的高度。

我们先来思考 UPDATE 操作。考虑以下几种情形:

  • 如果我们发现该区间未储存直线,那么我们直接赋值即可。
  • 如果我们发现原先有了直线,那么求出两端点上两直线的函数值,待加入直线在\(l\)上的函数值为\(ff\),在\(r\)上的函数值为\(fb\),待比较直线在\(l\)上的函数值为\(gf\),在\(r\)上的函数值为\(gb\)。我们考虑下面几种情况:
    • 如果两直线平行(也就是\(ff,fb\)与\(gf,gb\)有相同的大小关系时),那么我们直接比较高度取最高\最低的直线。
    • 如果两直线有交点,我们求出交点的横坐标\(intx\),分别进行考虑进行左右儿子更新即可。

例题

// BZ1568.cpp
#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define lson (p << 1)
#define rson ((p << 1) | 1)
using namespace std;
const int MAX_Q = 100020, MAX_N = 50010, ENDPOINT = 50000;
int n, tmpx;
char opt[10];
double lnk[MAX_N << 2], lnb[MAX_N << 2], tmpw, tmpz;
bool tag[MAX_N << 2];
double intersect(double k1, double b1, double k2, double b2) { return 1.0 * (b2 - b1) / (k1 - k2); }
void update(double k, double b, int l, int r, int p)
{
    if (tag[p])
    {
        double ff = k * l + b, gf = lnk[p] * l + lnb[p], fb = k * r + b, gb = lnk[p] * r + lnb[p];
        if (ff <= gf && fb <= gb)
            return;
        else if (ff >= gf && fb >= gb)
        {
            lnk[p] = k, lnb[p] = b;
            return;
        }
        double intx = intersect(k, b, lnk[p], lnb[p]);
        if (ff >= gf)
            if (intx <= mid)
                update(k, b, l, mid, lson);
            else
                update(lnk[p], lnb[p], mid + 1, r, rson), lnk[p] = k, lnb[p] = b;
        else if (intx > mid)
            update(k, b, mid + 1, r, rson);
        else
            update(lnk[p], lnb[p], l, mid, lson), lnk[p] = k, lnb[p] = b;
    }
    else
        lnk[p] = k, lnb[p] = b, tag[p] = true;
}
double query(int qx, int l, int r, int p)
{
    double ans = 0;
    if (tag[p])
        ans = max(ans, 1.0 * qx * lnk[p] + lnb[p]);
    if (l == r)
        return ans;
    if (qx <= mid)
        ans = max(ans, query(qx, l, mid, lson));
    else
        ans = max(ans, query(qx, mid + 1, r, rson));
    return ans;
}
int main()
{
    scanf("%d", &n);
    while (n--)
    {
        scanf("%s", opt + 1);
        if (opt[1] == 'Q')
            scanf("%d", &tmpx), printf("%d\n", (int)(query(tmpx, 1, ENDPOINT, 1) / 100.0));
        else
            scanf("%lf%lf", &tmpw, &tmpz), update(tmpz, tmpw - tmpz, 1, ENDPOINT, 1);
    }
    return 0;
}

数颜色题解

题意

给定一个长度为n的序列A[],你需要回答q个询问。每次给定两个端点,询问区间内不同颜色的个数。n, q < 1e5。

主要思路

可以考虑建立主席树,以位置做版本下标,以颜色离散化后的值做下标,记录该颜色前缀个数,然后版本信息相减即可。

BZOJ1176:「Balkan2007」Mokia 题解

主要思路

这道题的\(w\)很大,且要求出二维和。一种可行的办法就的是嵌套数据结构,第一想法是树状数组套线段树动态开点,时间复杂度为\(O(log^2 m)\)。但是空间依旧很大,可以发现线段树空间庞大,可以考虑替换为平衡树,空间极大减小,且时间复杂度不变。

进一步优化可以考虑对下标进行哈希。