Loading

【题解】P8150 再会 | Sayounara

迟来一年的道别,那就祝 Mivik 神仙大学生活天天开心吧 qwq

再会啦!

思路来自 @€€£

思路

三分。

假如要知道每个位置上具体的值,最直接的想法是通过 query 操作差分。

但是这样做的问题在于不能保证最小值的位置,考虑一些简单的情况:求最小值相邻位置的值。

在知道最小值的前提下,直接 query 一次就可以解决。

于是我们知道了三个位置的值。如果要再往两侧拓展,假设我们把 query 的边界设定在最小值的位置,那么每次减去的值是已知的定值,并且询问的区间中除去当前位置的总和也是已知的,故而可以再通过一次 query 操作得知当前位置的值。

所以在知道最小值及其位置的前提下,我们可以通过 \(O(n)\) 次操作得知答案。现在的问题转化成如何在一定次数的操作内求出最小值。

看到交互大概可以查出成分是二分 / 三分,根据题解知道 数据范围的式子化简出来的询问次数上界是 \(n + 300\).

因为所有数据的 \(n\) 都是 \(10^6\)\(300\) 大概在 \(O(log^2 n)\) 的级别,三分的复杂度可以接受。可以往三分求最小值位置的方向想。

直觉上先求出最小值的位置比较简单,因为最小值的具体取值可以通过一次 get 操作直接得到。

这里的方法需要一点奇思妙想。

令三分的两个临界点分别为 \(p1, p2\)。假如我们可以通过 query 操作排除最小值的位置,那么思路应该是考虑用两次 query 差分,先钦定式子前面的区间总和一定,通过后面减去的区间最小值的差来判断最小值所在的位置。

不妨令这个恒定的区间为 \([p1, p2]\)。我们可以构造两个差 \(query(l, p2) - query(l, p1 - 1)\)\(query(p1, r) - query(p2 + 1, r)\)

它们的差化简出来是:

\[\begin{aligned} &(\sum\limits_{k = l}^{p2} a_k - \min_{k = l}^{p2} a_k - \sum\limits_{k = l}^{p1 - 1} a_k + \min_{k = l}^{p1 - 1} a_k) - (\sum\limits_{k = p1}^{r} a_k - \min_{k = p1}^{r} a_k - \sum\limits_{k = p2 + 1}^{r} a_k + \min_{k = p2 + 1}^{r} a_k) \\ &= (\sum\limits_{k = p1}^{p2} a_k + min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k) - (\sum\limits_{k = p1}^{p2} a_k + \min_{k = p2 + 1}^{r} a_k - \min_{k = p1}^{r} a_k) \\ &= min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k - \min_{k = p2 + 1}^{r} a_k + \min_{k = p1}^{r} a_k \end{aligned} \]

简单分类讨论一下最小值的位置:

  1. 最小值在 \([p1, p2]\)

    \(min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k - \min_{k = p2 + 1}^{r} a_k + \min_{k = p1}^{r} a_k = min_{k = l}^{p1 - 1} a_k - \min_{k = p2 + 1}^{r}\).

    又因为 \(a\) 每个位置的取值各不相同,所以原式必不等于 \(0\).

    所以当原式等于 \(0\) 时,最小值必定不在 \([p1, p2]\) 中。

  2. 最小值在 \([l, p1)\)

    \(min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k - \min_{k = p2 + 1}^{r} a_k + \min_{k = p1}^{r} a_k = \min_{k = p1}^{r} - \min_{k = p2 + 1}^{r} a_k < 0\).

  3. 最小值在 \((p2, r]\)

    \(min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k - \min_{k = p2 + 1}^{r} a_k + \min_{k = p1}^{r} a_k = min_{k = l}^{p1 - 1} a_k - \min_{k = l}^{p2} a_k > 0\).

最后剩下一两个值的时候,直接 get 得到最小值就行。

最后用 query 差分一遍就能得到答案。

代码

#include <cstdio>
#include <cstdint>
#include <vector>
#include <algorithm>

typedef uint32_t ui;
typedef uint64_t ull;

const int maxn = 1e6 + 5;

int n = 1e6;
ui seq[maxn], tmp[3];
ull res[maxn], q[maxn];
bool vis[maxn];

uint64_t query(int l, int r);
uint32_t get(int x);

int get_minp()
{
    int len = 0;
    for (int i = 1; i <= n; i++)
        if (!vis[i]) seq[++len] = i;
    if (len <= 2)
    {
        for (int i = 1; i <= len; i++) tmp[i] = get(seq[i]), res[seq[i]] = (ull)tmp[i];
        std::sort(tmp + 1, tmp + len + 1);
        for (int i = 1; i <= len; i++)
            if (res[seq[i]] == tmp[1]) return seq[i];
        return -1;
    }
    int blk = len / 3, p1 = 1 + blk, p2 = len - blk;
    ull v1 = query(1, seq[p2]) - query(1, seq[p1] - 1);
    ull v2 = query(seq[p1], n) - query(seq[p2] + 1, n);
    if (v1 == v2) for (int i = p1; i <= p2; i++) vis[seq[i]] = true;
    else if (v1 > v2) for (int i = 1; i < p1; i++) vis[seq[i]] = true;
    else for (int i = p2 + 1; i <= len; i++) vis[seq[i]] = true;
    return get_minp();
}

std::vector<ui> recover(int n)
{
    int pos = get_minp();
    for (int i = pos + 1; i <= n; i++) q[i] = query(pos, i), res[i] = q[i] - q[i - 1];
    for (int i = pos - 1; i >= 1; i--) q[i] = query(i, pos), res[i] = q[i] - q[i + 1];
    std::vector<ui> ans;
    for (int i = 1; i <= n; i++) ans.push_back((ui)res[i]);
    return ans;
}

对不起了,大家,你们看到这个帖子的时候,我可能已经走了……不对,是已经走了
抗了几年,想不到还是抗不下去了,4月份那次的手术还是失败了呢……
现在也是,连自己打字也没办法了呢……
在这个论坛,认识了你们,我真的很高兴
但是我最大的遗憾就是QQ账号没了,不能跟组里的群里的管理的大家亲口说再见呢
对不起了,眠羽、白莲、hgs、狗狗、计时器、yuu、星期六……等
其实还有很多话想说的
但是,塞在心里都说不出来了
而且,替我打字记录这段话的某人也已经哭得不像话了呢,真丢脸,哈哈
最后,祝你们身体健康呀,而且要多点去体检体检哟,别像我这样,晚了就晚了
爱你们
如水如星~~我永远在这里呼啸
清水星

2023 年 7 月 12 日 15:30 PM,清水星姐姐还是离开了我们。

一周后,亲友代其在喵玉殿发出最后的告别 再见了,大家

希望她能在幻想乡开开心心地生活,借用这道题的 “再会” 来表达痛心。

再会,清水星!

posted @ 2023-07-31 03:14  kymru  阅读(29)  评论(1编辑  收藏  举报