loading...

7.22 贪心,细节处理

[CEOI 2024] 洒水器

给定 \(n\) 个洒水器的位置和 \(m\) 个花的位置,一个位于 \(x\) 的洒水器可以选择往左喷还是往右喷,设半径为 \(K\),则可以覆盖 \([x-K, x]\)\([x, x+K]\),所有洒水器的 \(K\) 都是一样的,求要覆盖所有花时 \(K\) 的最小值,使得存在某个方案满足条件。

\(n, m \le 10^5\)

为什么不是紫。

二分答案。

先尽量使洒水器向左喷,如果左边能够喷到的范围已经被完全覆盖,则向右喷。反例如下(s 为洒水器):

f..sfs..f

\(K=5\) 时,可以想到左边的 s 往右喷,右边的往左喷可以达到浇灌效果。但是刚刚的策略会死,因为会先让左边的往左喷,右边的还是会往左喷。最后一个花就喷不到了。

考虑引入反悔,如果一个往左喷的洒水器完全毙掉了前面的某个洒水器,则将前面的洒水器反转方向。

继续考虑构造反例:有没有可能这个往左喷的洒水器往右喷会更好呢,试一下

f..sfs.f.sf

还是考虑刚刚的过程,我们先会:

Step 1

f..sfs.f.sf
   L

Step 2: flip

f..sfs.f.sf
   R L

Step 3

f..sfs.f.sf
   R L   L

不合法,判断为 \(-1\)

但实际上我们有合法方案:

f..sfs.f.sf
   L R   L

所以考虑进一步反悔,如果当前为 L 的位置的覆盖部分能够被左右两边的洒水器 L 完全覆盖掉,那么这个地方实际上是应该为 R 更优的。所以我们先暂时不翻转,改成打一个标记,然后倒着遍历如果遇到有标记的就将其变为 R,并去除掉它上一个位置的标记。

[THUPC 2023 初赛] 喵了个喵 II

给定长为 \(4n\) 的序列,其中 \(1 \sim n\) 分别出现 \(4\) 次。问能否将其划分为两个相等的子序列。

\(n \le 5 \times 10^4\)

考虑刻画一个更容易维护的条件。

先考虑一个弱化版问题,如果一个长为 \(2n\) 的序列,其中 \(1 \sim n\) 分别出现 \(2\) 次,两次分别为 \(\ell_i, r_i(\ell _i < r_i)\),则合法的充要条件是不存在 \(i \neq j\),使得 \([\ell _i, r_i] \subseteq [\ell _j, r_j]\)。这有什么可以说的,直接排序判断就行了。

回到本题,对于 \(i\) 出现的 \(4\) 个位置记做 \(a_i < b_i < c_i < d_i\),则它们在子序列的分配方式如下:

  • \((a_i, b_i), (c_i, d_i)\)
  • \((a_i, c_i), (b_i, d_i)\)
  • \((a_i, d_i), (b_i, c_i)\)

较弱化版问题多了几个选择,选择过后仍然是若干个区间,合法条件其实与弱化版问题相似。

\(3\) 个选择里面 \(i\) 自己就出现了包含的问题,模一模可以发现这种选择不会优于第 \(1\)\(2\) 种选择,也就是如果第三种选择合法,一定可以用 \(1,2\) 选择替代。

所以我们只有两种选择:

  • \((a_i, b_i), (c_i, d_i)\),不相交
  • \((a_i, c_i), (b_i, d_i)\),相交不包含

其实到这里已经能够想到只有两个选择,可以使用 2-SAT 了。难点在于建边:我们需要对于选择点 \((\ell_1, r_1),(\ell_2, r_2)\)\((\ell_1', r_1'),(\ell_2', r_2')\),如果这四个区间中存在两个有包含关系,则说明合法方案中不能有这两个选择。

注意到这是一个二维偏序的关系,套用主席树优化建图可以做到 \(\mathcal O(n \log n)\) 但是常数 \(>100\)

但是有一个非常细节的点!主席树不能只复制叶子,而应当把叶子的邻接表顺带复制了,否则你会将 no 输出为 yes,或者方案输出错误,因为限制被减少了。由于本题的特殊性,叶子处的邻接表最多只有两个点,可以直接复制,不影响复杂度。或者你也可以直接在在复制节点和原节点之间连边,达到继承的效果。

叶子连边的代码实现,删去了调试语句
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define drep(i, r, l) for (int i = r; i >= l; --i)
#define forG(i, x, G) for (int i = G.he[x]; i; i = G.ne[i])
#define LL long long
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define FRE(p) freopen(#p".in", "r", stdin), freopen(#p".out", "w", stdout)
#define PII pair<int,int>
#define eb emplace_back
using namespace std;
const int N = 2e5+5, M = N*100, inf = 0x3f3f3f3f;
const LL V = 1e18;
int n, a[N];
int p[N][4], cnt;
struct interval {
    int l, r, idx;
} itv[N];
int head[N*50], ver[M], ne[M], tot;
inline void adde(int u, int v, int del = 0) {
    u += del, v += del;
    ver[++tot] = v, ne[tot] = head[u], head[u] = tot;
}
int rt[N], ls[N*50], rs[N*50], trc, pre[N*50];
int update(int p, int l, int r, int x) {
    int p_ = ++trc;
    ls[p_] = ls[p], rs[p_] = rs[p];
    if (l == r) return pre[p_] = p, p_;
    int mid = (l+r)>>1;
    x <= mid ? (ls[p_] = update(ls[p], l, mid, x)) : (rs[p_]=update(rs[p], mid+1, r, x));
    return p_;
}
int find(int p, int l, int r, int x) {
    if (!p) return 0;
    if (l == r) return p;
    int mid = (l+r)>>1;
    return x <= mid ? find(ls[p], l, mid, x) : find(rs[p], mid+1, r, x);
}
inline int rev(int x) { return x > n ? x-n : x+n; }
void edge(int p, int l, int r, int L, int R, int v) {
    if (!p) return;
    if (L <= l && r <= R) {
        adde(v, p+(n<<1));
        adde(p+(n<<1)+trc, rev(v));
        return;
    }
    int mid = (l+r)>>1;
    if (L <= mid) edge(ls[p], l, mid, L, R, v);
    if (mid < R) edge(rs[p], mid+1, r, L, R, v);
}
int dfn[N*50], dfc, low[N*50], scc[N*50], scnt, st[N*50], tp;
void tarjan(int x) {
    st[++tp] = x, dfn[x] = low[x] = ++dfc;
    for (int i = head[x], y; i; i = ne[i]) {
        if (!dfn[y = ver[i]]) tarjan(y), low[x] = min(low[x], low[y]);
        else if (!scc[y]) low[x] = min(low[x], dfn[y]);
    }
    if (low[x] == dfn[x]) {
        int y; ++scnt;
        do scc[y = st[tp--]] = scnt; while (y ^ x);
    }
}
int res[N];
signed main() {
    FASTIO;
#ifdef LOCAL
    FRE(test);
#endif
    cin >> n;
    rep(i, 1, (n<<2)) cin >> a[i];
    rep(i, 1, (n<<2))
        rep(j, 0, 3) if (!p[a[i]][j]) {
            p[a[i]][j] = i;
            break;
        }
    rep(i, 1, n) {
        itv[++cnt] = {p[i][0], p[i][1], i};
        itv[++cnt] = {p[i][2], p[i][3], i};
        itv[++cnt] = {p[i][0], p[i][2], i+n};
        itv[++cnt] = {p[i][1], p[i][3], i+n};
    }
    sort(itv+1, itv+cnt+1, [](const interval& a, const interval& b) {
        return a.r < b.r; });
    rep(i, 1, cnt) rt[i] = update(rt[i-1], 1, n<<2, itv[i].l);
    // 绘制主席树优化建图
    rep(i, 1, trc) {
        if (pre[i]) adde(i, pre[i], (n<<1)), adde(pre[i], i, (n<<1)+trc); // 细节:继承上一个节点的邻接表!
        if (ls[i]) adde(i, ls[i], (n<<1)), adde(ls[i], i, (n<<1)+trc);
        if (rs[i]) adde(i, rs[i], (n<<1)), adde(rs[i], i, (n<<1)+trc);
    }
    rep(i, 1, cnt) {
        // 叶子上挂节点
        int p = find(rt[i], 1, n<<2, itv[i].l);
        adde(p+(n<<1), rev(itv[i].idx)), adde(itv[i].idx, p+trc+(n<<1));
        // 查询
        edge(rt[i-1], 1, n<<2, itv[i].l, itv[i].r, itv[i].idx);
    }
    int ts = (n+trc<<1);
    rep(i, 1, ts) if (!dfn[i]) tarjan(i);
    rep(i, 1, n)
        if (scc[i] == scc[i+n]) return cout << "No", 0;
        else {
            if (scc[i] < scc[i+n]) {
                res[p[i][0]] = res[p[i][2]] = 0;
                res[p[i][1]] = res[p[i][3]] = 1;
            } else {
                res[p[i][0]] = res[p[i][1]] = 0;
                res[p[i][2]] = res[p[i][3]] = 1;
            }
        }
    cout << "Yes\n";
    rep(i, 1, (n<<2)) cout << res[i];
    return 0;
}
posted @ 2026-07-22 21:40  goldspade  阅读(2)  评论(0)    收藏  举报