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;
}

浙公网安备 33010602011771号