Solution -「CF1373G」Pawns

小清新线段树题((

每个位置的边只能向靠右的三个方向走,最后要走到一条基准线上。即对于一个点 \((x, y)\),它最后应该落在 \((k, y + |k - x|)\)

士兵可以一个一个进行移动,所以互相的移动行程不会互相干扰。考虑利用这个性质将题目从二维转换为一维。

即:有一队士兵排好队,每个士兵有一个预选定的位置。当轮到第 \(i\) 位士兵选位置时,若预选定位置有其它士兵,则选择当前选定位置右边紧挨着的位置,直到当前选定位置上不再有其他士兵后站入该位置。试问至少需要新加入多少个格子可以收容所有士兵。

盲目感觉这种“当前放不了就考虑下一个”的题目常常会考虑维护一些后缀,所以接下来我们引入后缀。

如果构造一个数组 \(A\),且初始时 \(\forall A_i = -1 \space \mathrm{s.t.} i \in [1, n]\)。且定义数组 \(f\),表示有 \(f_i\) 个士兵预选定了 \(i\) 这个位置。则答案为序列 \(A + f\) 的后缀和最大值。(建议自己手 Rua 一下。

但对于 \(i > n\) 的部分,这样是不好处理的。因为在原题中,这些位置也应该参与到“新加入位置”的贡献中。于是更改 \(A\) 的构造方式。

\(i \in [1, n]\) 的部分,我们直接令 \(A_i = -(n - i + 1)\),即直接储存后缀。

\(i > n\) 的部分,我们尝试分析其贡献。如果要使用 \(n + 2\) 这个位置,则我们需要先添加一个 \(n + 1\) 的位置,也就是第 \(n + 2\) 项初值应该为 \(1\)。同理可知 \(n + 1 \dots n + n\) 等位置,故也可令 \(i > n\) 的部分中 \(A_i = -(n - i + 1)\)

不难发现,现在答案为 \(\max \{ A_i + g_i \} \space \mathrm{s.t.} i \in [1, w]\),其中 \(w\) 是预选定位置最靠右的一个,\(g\)\(f\) 的后缀序列。

\(f\) 是动态更新的,每次更新会先区间修改 \(g\),最终作用于 \(A + f\)。故 \(A + g\) 可以直接使用区修区查的线段树维护。\(w\) 使用 set,加一个计数器维护即可。

总时间复杂度 \(O(n \log n)\)。这里使用的是常数较小的 zkw 线段树。

#include <map>
#include <set>
#include <cstdio>
using namespace std;

int Abs (int x) { return x < 0 ? -x : x; }
int Max (int x, int y) { return x > y ? x : y; }
int Min (int x, int y) { return x < y ? x : y; }

int Read () {
    int x = 0, k = 1;
    char s = getchar ();
    while (s < '0' || s > '9') {
        if (s == '-')
            k = -1;
        s = getchar ();
    }
    while ('0' <= s && s <= '9') 
        x = (x << 3) + (x << 1) + (s ^ 48), s = getchar ();
    return x * k;
}

void Write (int x) {
    if (x < 0)
        putchar ('-'), x = -x;
    if (x > 9)
        Write (x / 10);
    putchar (x % 10 + '0');
}

void Print (int x, char s) { Write (x), putchar (s); }

const int Inf = 1e9;
const int Maxn = 4e5 + 5;

set <int> s;
map <int, int> Vis[Maxn];
int a[Maxn], Tr[Maxn * 3], Lazy[Maxn * 3], Cnt[Maxn], p;

void Make_Tree (int n) {
    p = 1;
    while (p < n + 2)
        p <<= 1;
	Tr[p + n + 1] = Tr[p] = -Inf;
    for (int i = 1; i <= n; i++)
        Tr[p + i] = a[i];    
    for (int i = p + n + 1; i > 1; i--)
        Tr[i >> 1] = Max (Tr[i], Tr[i ^ 1]);
}

void Update (int l, int r, int x) {
    for (l += p - 1, r += p + 1; (l ^ r) != 1; l >>= 1, r >>= 1) {
        if (!(l & 1))
            Tr[l ^ 1] += x, Lazy[l ^ 1] += x;
        if (r & 1)
            Tr[r ^ 1] += x, Lazy[r ^ 1] += x;
        Tr[l >> 1] = Max (Tr[l], Tr[l ^ 1]) + Lazy[l >> 1];
        Tr[r >> 1] = Max (Tr[r], Tr[r ^ 1]) + Lazy[r >> 1];
    }
    for (; l > 1; l >>= 1)
        Tr[l >> 1] = Max (Tr[l], Tr[l ^ 1]) + Lazy[l >> 1];
}

int Query (int l, int r) {
    int Res = -Inf, Resl = -Inf, Resr = -Inf;
    for (l += p - 1, r += p + 1; (l ^ r) != 1; l >>= 1, r >>= 1) {
        if (!(l & 1))
            Resl = Max (Resl, Tr[l ^ 1]);
        if (r & 1)
            Resr = Max (Resr, Tr[r ^ 1]);
        Resl += Lazy[l >> 1], Resr += Lazy[r >> 1];
    }
    Res = Max (Resl, Resr);
    for (; l > 1; l >>= 1)
        Res += Lazy[l >> 1];
    return Res;
}

int main () {
    int n = Read (), k = Read (), m = Read ();
    for (int i = 1; i <= 2 * n; i++)
        a[i] = -(n - i + 1);
    Make_Tree (n << 1);
    for (int i = 1, x, y; i <= m; i++) {
        x = Read (), y = Read ();
        if (!Vis[x][y]) {
            if (!Cnt[y + Abs (x - k)])
                s.insert (y + Abs (x - k));
            Cnt[y + Abs (x - k)]++;
            Update (1, y + Abs (x - k), 1), Vis[x][y] = true;
        }
        else {
            Cnt[y + Abs (x - k)]--;
            if (!Cnt[y + Abs (x - k)])
                s.erase (y + Abs (x - k));            
            Update (1, y + Abs (x - k), -1), Vis[x][y] = false;
        }
        if (s.empty ())
            Print (0, '\n');
        else 
            Print (Max (0, Query (1, *s.rbegin ())), '\n');   
    }
    return 0;
}
posted @ 2022-07-05 19:38  STrAduts  阅读(38)  评论(0)    收藏  举报