Loading

题解:[ICPC 2023 Seoul R] Farm

前排声明:下文是一种 \(\mathcal O(n\log n)\) 的线段树合并做法,本题应该还存在一种线性做法(至少离散化后线性,或者带值域线性 \(\mathcal O(n+m+V)\)),但是我还不会。

首先可以发现题目中的“矩形不相交”限制不重要,任意一种允许相交的矩形覆盖方案都可以调整为一种不相交且矩形数量一样的覆盖方案。

考虑平面上的问题转化为序列问题。所有竖直边界所在直线将图形分成了 \(N=\frac{m}{2}-1\) 个块,将从左往右第 \(i\) 块编号为 \(i\)。设第 \(i\) 块上边界纵坐标为 \(b_i\),第 \(i\) 块中所有害虫纵坐标最大值为 \(a_i\),离散化后可以认为 \(1\le b_i\le N,0\le a_i\le N\) 其中 \(a_i=0\) 表示第 \(i\) 块没有害虫。

一个问题是第 \(i\) 块和 \(i+1\) 块边界上的害虫划在哪边,样例 1 就出现了这种情况。可以发现应该划在 \(b_i,b_{i+1}\) 较大值那边。证明考虑不妨设 \(b_i\ge b_{i+1}\),那么如果存在一种将这个害虫划在 \(i+1\) 时的矩形覆盖方案,如果这个害虫所在矩形包含第 \(i\) 块则这个方案在划在 \(i\) 时也合法,否则这个矩形覆盖的块一定是 \([i+1,j]\),这是这个矩形上边界不超过 \(b_{i+1}\le b_i\),故可以将其调整为覆盖块 \([i,j]\) 且上边界不变。

另一种理解方式是将每条 \(x=i\) 直线作为一个“块”,考虑这条直线上最高的害虫和上边界,得到 \(a_{1\sim V},b_{1\sim V}\),其中 \(V\) 为值域。前面的划分实际上相当于将相邻的相等的 \(b\) 合并了。

现在问题就变成了给定 \(a_{1\sim N},b_{1\sim N}\),每次可以选择 \([l,r]\),将 \(l\le i\le i\)\(a_i\le \min_{j=l}^r\{b_j\}\)\(a_i\) 设为 \(0\),求至少多少次能把 \(a\) 变为全 \(0\)

这种覆盖方式的常见做法是对 \(b\) 建小根笛卡尔树,那么在点 \(x\) 可以覆盖 \(x\) 子树中所有 \(a_y\le b_x\)\(y\)。考虑树形 dp,设 \(f_{x,i}\) 表示 \(x\) 子树内,还没被覆盖的 \(a_y\) 最大值为 \(i\),至少需要几次操作。分两步转移:

\[f_{x,\max(i,a_x)}=\min(f_{ls,i}+\min_{j\le i}\{f_{rs,j}\},f_{rs,i}+\min_{j\le i}\{f_{ls,j}\})\\ f_{x,0}=\min_{0\le i\le b_x}\{f_{x,i}\}+1 \]

然后这就是比较经典的复杂线段树合并问题,可以做到时空复杂度 \(\mathcal O(n\log n)\)

如果没有做过这种线段树合并可以参考 [NOI2020] 命运,这个题除线段树合并的部分更复杂,线段树合并的部分和本题很类似(一个是最优化,另一个是计数)。

实现细节留作练习,下面是一份参考代码:

#include <bits/stdc++.h>

int read()
{
	int s = 0; int f = 1, c = getchar();
	for (; !isdigit(c); c = getchar()) f ^= (c == '-');
	for (; isdigit(c); c = getchar()) s = s * 10 + (c ^ 48);
	return f ? s : -s;
}
template<typename T>
void write(T x, char end = '\n')
{
	if (x < 0) putchar('-'), x = -x;
	static int d[100]; int cur = 0;
	do { d[++cur] = x % 10; } while (x /= 10);
	while (cur) putchar(d[cur--] ^ 48);
	putchar(end);
}
const int inf = 0x3f3f3f3f;
template<typename T> void Fmax(T& x, T y){ if (x < y) x = y; }
template<typename T> void Fmin(T& x, T y){ if (y < x) x = y; }
const int MAXN = 100005;
int n, a[MAXN], b[MAXN], V;
void input()
{
    int _n = read(), _m = read();
    int v[MAXN], pos[MAXN];
    for (int i = 1; i <= _n; i++) v[i] = read(); 
    n = _n / 2 - 1;
    for (int i = 1; i <= n; i++) b[i] = v[i * 2];
    for (int i = 1; i <= n; i++) pos[i] = v[i * 2 + 1];
    memset(a + 1, -1, n << 2);
    for (int i = 1; i <= _m; i++)
    {
        int x = read(), y = read();
        int id = std::lower_bound(pos + 1, pos + n + 1, x) - pos;
        if (x == pos[id] && id < n && b[id + 1] > b[id]) id++;
        Fmax(a[id], y);
    }
    memcpy(v + 1, b + 1, n << 2);
    std::sort(v + 1, v + n + 1);
    V = std::unique(v + 1, v + n + 1) - v - 1;
    v[0] = -1;
    for (int i = 1; i <= n; i++)
    {
        a[i] = std::lower_bound(v, v + V + 1, a[i]) - v; 
        b[i] = std::lower_bound(v + 1, v + V + 1, b[i]) - v; 
    }
}
int ls[MAXN], rs[MAXN], root;
void build()
{
    int st[MAXN], tp = 0;
    for (int i = 1; i <= n; i++)
    {
        int lst = 0;
        while (tp && b[st[tp]] >= b[i])
        {
            if (lst) rs[st[tp]] = lst;
            lst = st[tp--];
        }
        if (lst) ls[i] = lst;
        st[++tp] = i;
    }
    root = st[1];
    for (int i = 1; i < tp; i++) rs[st[i]] = st[i + 1];
}
namespace SGT
{
    const int MAXS = 1000005;
    int sgt[MAXS], ls[MAXS], rs[MAXS], tot, lzy[MAXS];
    void init(){ tot = 0, sgt[0] = inf; }
    void maintain(int x){ sgt[x] = std::min(sgt[ls[x]], sgt[rs[x]]); }
    int New(){ sgt[++tot] = inf, lzy[tot] = ls[tot] = rs[tot] = 0; return tot; }
    void hard(int x, int v){ Fmin(sgt[x] += v, inf), Fmin(lzy[x] += v, inf); }
    void spread(int x)
    {
        if (lzy[x])
        {
            if (ls[x]) hard(ls[x], lzy[x]);
            if (rs[x]) hard(rs[x], lzy[x]);
            lzy[x] = 0;
        }
    }
    void modify(int &x, int l, int r, int pos, int v)
    {
        if (!x) x = New();
        if (l == r) return Fmin(sgt[x], v), void();
        int mid = (l + r) >> 1; spread(x);
        if (pos <= mid) modify(ls[x], l, mid, pos, v);
        else modify(rs[x], mid + 1, r, pos, v);
        maintain(x); 
    }
    int merge(int x, int y, int l, int r, int prex = inf, int prey = inf)
    {
        if (!x && !y) return 0;
        if (!y) return hard(x, prey), x;
        if (!x) return hard(y, prex), y;
        if (l == r) return sgt[x] = std::min(sgt[x], prex) + std::min(sgt[y], prey), x;
        int mid = (l + r) >> 1; spread(x), spread(y);
        rs[x] = merge(rs[x], rs[y], mid + 1, r, std::min(prex, sgt[ls[x]]), std::min(prey, sgt[ls[y]]));
        ls[x] = merge(ls[x], ls[y], l, mid, prex, prey);
        maintain(x); return x;
    }
    int query(int x, int l, int r, int qr)
    {
        if (!x) return inf;
        if (r <= qr) return sgt[x];
        int mid = (l + r) >> 1; spread(x);
        return qr <= mid ? query(ls[x], l, mid, qr) : std::min(sgt[ls[x]], query(rs[x], mid + 1, r, qr));
    }
    void show(int x, int l, int r)
    {
        if (!x) return ;
        if (l == r) return printf("%d: %d\n", l, sgt[x]), void();
        int mid = (l + r) >> 1; spread(x);
        show(ls[x], l, mid), show(rs[x], mid + 1, r);
    }

}
int rt[MAXN];
void dfs(int x)
{
    SGT::modify(rt[x], 0, V, a[x], 0);
    if (ls[x]) dfs(ls[x]), rt[x] = SGT::merge(rt[x], rt[ls[x]], 0, V);
    if (rs[x]) dfs(rs[x]), rt[x] = SGT::merge(rt[x], rt[rs[x]], 0, V);
    int v = SGT::query(rt[x], 0, V, b[x]) + 1;
    SGT::modify(rt[x], 0, V, 0, v);
}
int main()
{
    input();
    build();
    SGT::init();
    SGT::modify(rt[0], 0, V, 0, 0);
    dfs(root);
    write(SGT::query(rt[root], 0, V, 0));
	return 0;
}


posted @ 2026-04-27 10:47  complexor  阅读(5)  评论(0)    收藏  举报