P11983 [JOIST 2025] 展览会 3 题解

Description

JOI 美术馆计划近期举办一场绘画展览。馆方拥有编号为 11NNNN 幅画作,其中画作 ii1iN1 \leq i \leq N)的美观值AiA_i。在展览中这些画作将排成一行展示,但具体排列顺序尚未确定。

共有 MM 家杂志将对展览进行报道。这些杂志按影响力从大到小依次编号为 11MM。每家杂志将发布展览中某一连续段画作的摄影照片。具体来说,杂志 jj1jM1 \leq j \leq M)将发布排列中从左数第 Lj,Lj+1,,RjL_j, L_j + 1, \ldots, R_j 幅画作的照片。杂志 jj1jM1 \leq j \leq M)报道的吸引力定义为该杂志所覆盖画作的最大美观值。

JOI 君作为 JOI 美术馆的馆长,希望通过排列画作使得这些杂志的报道更具吸引力,从而吸引更多参观者。由于影响力更大的杂志能触达更多受众,他优先希望提升更具影响力杂志的报道吸引力。

具体而言,设 bjb_j 为杂志 jj1jM1 \leq j \leq M)报道的吸引力,则 JOI 君希望排列画作,使得序列 b=(b1,b2,,bM)b = (b_1, b_2, \ldots, b_M) 的字典序最大化。

在这里,对于不同的数列 b=(b1,b2,,bM)b = (b_1, b_2, \ldots, b_M)b=(b1,b2,,bM)b' = (b'_1, b'_2, \ldots, b'_M),所谓“bb 在字典序上大于 bb'”,是指存在满足 bkbkb_k \neq b'_k 的最小下标 kk1kM1 \leq k \leq M),且对于该 kkbk>bkb_k > b'_k

请编写一个程序,根据待展览画作的信息和报道展览的杂志信息,计算当画作排列使序列 b=(b1,b2,,bM)b = (b_1, b_2, \ldots, b_M) 字典序最大化时,每家杂志报道的吸引力。

1N105,1M105,1AiN1 ≤ N ≤ 10^5,1 ≤ M ≤ 10^5,1 ≤ A_i ≤ N

Solution

下面假定 n,mn,m 同阶。

f(S)f(S) 表示要满足在 SS 集合内的区间里每个区间至少有一个点的最少总点数。

首先肯定是贪心地给编号最小的区间 ii 选择最大的 vv,使得 f(Sv{[li,ri]})cntvf(S_v\cup\{[l_i,r_i]\})\leq cnt_v,并让 SvSv{[li,ri]}S_v\leftarrow S_v\cup\{[l_i,r_i]\}

f(S)f(S) 的话就直接按照右端点从小到大排序,如果当前区间 [l,r][l,r] 不包含任何一个点,就在 rr 处放一个点。最后放的点数就是答案。

可以发现这么做选的第 ii 个点的位置 srisr_i 实际上是所有可能方案中第 ii 个点的最大可能下标,倒过来按照左端点从大到小排序贪心能够得到最小可能下标 slisl_i。下面这个东西有两个性质:

性质 1:1i<k,sri<sli+1\forall 1\leq i<k,sr_i<sl_{i+1}

证明

如果存在 slisli+1srisri+1sl_i\leq sl_{i+1}\leq sr_i\leq sr_{i+1},找到最大的满足这个条件的 ii,则满足 sri+1<sli+2sr_{i+1}<sl_{i+2} 且存在一个区间 jj,满足 rj=srir_j=sr_ilj>srisli+1l_j>sr_i\geq sl_{i+1},所以倒过来贪心的时候 i+1i+1 这个位置的点一定会选到选 ljl_j 而不是 sli+1sl_{i+1}

性质 2:给 SS 加入一个区间 [L,R][L,R]f(S)f(S) 不变的充要条件是存在 ii,满足 [sli,sri][sl_i,sr_i][L,R][L,R] 有交。

证明

只需要说明存在一个选点方案,使得 [L,R][L,R] 包含其中至少一个点。

找到有交的 ii,所有 j<ij<i 的点放在 srjsr_jj>ij>i 的点放在 sljsl_j。这 k1k-1 个点的放置方案可以解决所有已经加到 SSlsli1l\leq sl_{i-1} 或者 rsri+1r\geq sr_{i+1} 的区间。容易发现剩下的区间 [l,r][l,r],一定满足 lslisrirl\leq sl_i\leq sr_i\leq r,所以第 ii 个点可以在 [sli,sri][sl_i,sr_i] 内随便选,放在和 [L,R][L,R] 的交里即可。

如果找不到这样的 ii 显然 f(S)f(S) 会增加。

考虑从大到小枚举 vv,设限制是 limlim,答案为 vv 的区间数是 cc,我们希望用 O(c×polylog(c))O(c\times\text{polylog}(c)) 的时间复杂度找到这些区间。

首先如果 f(S)f(S) 一直会增加的话 slisl_isrisr_i 会很不好维护,所以考虑先二分找到一个极长的前缀使得这个前缀的 f(S)f(S) 不超过 limlim

那么这个前缀的 f(S)f(S) 一定等于 limlim 或者这个前缀把所有区间选完了。

但是直接二分复杂度不是均摊的,因为二分的过程中会用至少 O(nlogn)O(n\log n) 的代价 check,cc 如果是 11 的话就会退化成 O(n2logn)O(n^2\log n)

考虑倍增,暴力 check,找到最大的 bb 满足 2bc2^b\leq c,那么 2bc<2b+12^b\leq c<2^{b+1},在这个区间内再进行二分即可。总时间复杂度变为 O(clog2n)O(c\log^2n),均摊就对了!

先维护出这个前缀的区间构成的 [sli,sri][sl_i,sr_i],我们用优先队列维护与某个区间 [sli,sri][sl_i,sr_i] 的相交的编号最小的区间。每次弹出队首并暴力更新 slisl_isrisr_i,由于 slisl_i 只会变大,srisr_i 只会变小,而分别总共只有 O(c)O(c) 种取值,所以 [sli,sri][sl_i,sr_i] 的历史总变化量为 O(c)O(c)。如果某个 [slj,srj][sl_j,sr_j] 被更新了,就再往优先队列里加入一个与当前的 [slj,srj][sl_j,sr_j] 相交的最小区间。

但是如果一个区间 [lj,rj][l_j,r_j] 完全包含了很多区间,且编号很小,它就会加入队列很多次,复杂度又不对了。但是注意到这个区间最终一定会被加入,并且不会对 [sli,sri][sl_i,sr_i] 造成影响,所以每次 [sli,sri][sl_i,sr_i] 更新前先把所有这样的区间删掉即可。剩下的区间如果有交则说明要么左端点在区间内,要么右端点在区间内,只会算至多两次。

单次的时间复杂度为 O(clog2n)O(c\log^2n),总的时间复杂度也就是 O(nlog2n)O(n\log^2n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#include <bits/stdc++.h>

// #define int int64_t

using pii = std::pair<int, int>;

const int kMaxN = 1e5 + 5;

int n, m;
int cnt[kMaxN], l[kMaxN], r[kMaxN], res[kMaxN];
bool vis[kMaxN];

struct List {
int fir, pre[kMaxN], nxt[kMaxN];
void init(int n) {
fir = 1;
for (int i = 1; i <= n; ++i) {
pre[i] = i - 1, nxt[i] = (i == n ? 0 : i + 1);
}
}
void del(int x) {
if (x == fir) fir = nxt[x];
if (pre[x]) nxt[pre[x]] = nxt[x];
if (nxt[x]) pre[nxt[x]] = pre[x];
}
std::vector<int> getid(int k) {
std::vector<int> v;
for (int i = fir; i && k; i = nxt[i], --k) v.emplace_back(i);
if (k) return {};
return v;
}
} list;

struct SGT1 {
std::vector<int> st[kMaxN * 4];
void ins(int x, int l, int r, int ql, int v) {
st[x].emplace_back(v);
if (l == r) return;
int mid = (l + r) >> 1;
if (ql <= mid) ins(x << 1, l, mid, ql, v);
else ins(x << 1 | 1, mid + 1, r, ql, v);
}
int query(int x, int l, int r, int ql, int qr) {
for (; st[x].size() && vis[st[x].back()]; st[x].pop_back()) {}
if (!st[x].size() || l > qr || r < ql) return 1e9;
else if (l >= ql && r <= qr) return st[x].back();
int mid = (l + r) >> 1;
return std::min(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr));
}
} sgt1, sgt2;

struct SGT2 {
std::vector<pii> st[kMaxN * 4];
void ins(int x, int l, int r, int ql, pii v) {
st[x].emplace_back(v);
if (l == r) return;
int mid = (l + r) >> 1;
if (ql <= mid) ins(x << 1, l, mid, ql, v);
else ins(x << 1 | 1, mid + 1, r, ql, v);
}
pii query(int x, int l, int r, int ql, int qr) {
for (; st[x].size() && vis[st[x].back().second]; st[x].pop_back()) {}
if (!st[x].size() || l > qr || r < ql) return {1e9, 0};
else if (l >= ql && r <= qr) return st[x].back();
int mid = (l + r) >> 1;
return std::min(query(x << 1, l, mid, ql, qr), query(x << 1 | 1, mid + 1, r, ql, qr));
}
} sgt3;

struct BIT {
int c[kMaxN];
void clear() { std::fill_n(c + 1, n, 1e9); }
void upd(int x, int v) {
for (; x <= n; x += x & -x) c[x] = std::min(c[x], v);
}
int qry(int x) {
int ret = 1e9;
for (; x; x -= x & -x) ret = std::min(ret, c[x]);
return ret;
}
void clear(int x) {
for (; x <= n; x += x & -x) c[x] = 1e9;
}
} bit1, bit2;

void del(int x) {
vis[x] = 1, list.del(x);
}

int calc(std::vector<int> id) {
if (!id.size()) return 1e9;
std::sort(id.begin(), id.end(), [&] (int i, int j) { return r[i] < r[j]; });
int cnt = 0, nowr = 0;
for (auto i : id) {
if (l[i] > nowr) ++cnt, nowr = r[i];
}
return cnt;
}

std::vector<std::pair<int, int>> getseg(std::vector<int> id) {
std::vector<int> sl, sr;
{
std::sort(id.begin(), id.end(), [&] (int i, int j) { return l[i] > l[j]; });
int nowl = 1e9;
for (auto i : id) {
if (r[i] < nowl) sl.emplace_back(l[i]), nowl = l[i];
}
std::reverse(sl.begin(), sl.end());
}
{
std::sort(id.begin(), id.end(), [&] (int i, int j) { return r[i] < r[j]; });
int nowr = 0;
for (auto i : id) {
if (l[i] > nowr) sr.emplace_back(r[i]), nowr = r[i];
}
}
assert(sl.size() == sr.size());
std::vector<std::pair<int, int>> seg;
for (int i = 0; i < sl.size(); ++i) seg.emplace_back(sl[i], sr[i]);
return seg;
}

void solve(int v, int cnt) {
int len = 0;
{ // 倍增找最长前缀
int k = 0;
for (int b = 1; b <= std::__lg(m); ++b) {
if (calc(list.getid(1 << b)) <= cnt) k = b;
else break;
}
int L = (1 << k), R = (1 << (k + 1)), res = L;
while (L + 1 < R) {
int mid = (L + R) >> 1;
if (calc(list.getid(mid)) <= cnt) L = res = mid;
else R = mid;
}
len = res;
}
auto vid = list.getid(len);
for (auto i : vid) del(i), bit1.upd(n - l[i] + 1, r[i]), bit2.upd(r[i], -l[i]);
auto seg = getseg(vid);
static int sl[kMaxN], sr[kMaxN];
// std::cerr << "??? " << v << ' ' << cnt << ' ' << seg.size() << '\n';
cnt = seg.size();
for (int i = 1; i <= seg.size(); ++i) std::tie(sl[i], sr[i]) = seg[i - 1];
sl[cnt + 1] = sr[cnt + 1] = n + 1;
std::priority_queue<pii, std::vector<pii>, std::greater<>> q;
std::function<void(int)> fix = [&] (int i) {
while (true) {
auto [rr, id] = sgt3.query(1, 1, n, 1, sl[i]);
assert(id <= m);
if (id && r[id] >= sr[i]) res[id] = v, del(id);
else break;
}
int id1 = sgt1.query(1, 1, n, sl[i], sr[i]), id2 = sgt2.query(1, 1, n, sl[i], sr[i]);
if (std::min(id1, id2) <= m) q.emplace(std::min(id1, id2), i);
};
std::function<void(int)> upd = [&] (int i) {
if (vis[i]) return;
int it = std::lower_bound(sr + 1, sr + 1 + cnt, l[i]) - sr;
if (it > cnt || std::max(sl[it], l[i]) > std::min(sr[it], r[i])) return;
del(i), vid.emplace_back(i);
bit1.upd(n - l[i] + 1, r[i]), bit2.upd(r[i], -l[i]);
for (int j = std::lower_bound(sr + 1, sr + 1 + cnt, r[i]) - sr; j <= cnt; ++j) {
// for (int j = 1; j <= cnt; ++j) {
int val = bit1.qry(n - sr[j - 1]);
assert(val <= sr[j]);
if (val == sr[j]) break;
sr[j] = val;
assert(sl[j] <= sr[j]);
fix(j);
}
for (int j = std::upper_bound(sl + 1, sl + 1 + cnt, l[i]) - sl - 1; j >= 1; --j) {
// for (int j = 1; j <= cnt; ++j) {
int val = -bit2.qry(sl[j + 1] - 1);
assert(val >= sl[j]);
if (val == sl[j]) break;
sl[j] = val;
assert(sl[j] <= sr[j]);
fix(j);
}
};
for (int i = 1; i <= cnt; ++i) {
while (true) {
auto [rr, id] = sgt3.query(1, 1, n, 1, sl[i]);
if (id && r[id] >= sr[i]) res[id] = v, del(id);
else break;
}
}
for (int i = 1; i <= cnt; ++i) fix(i);
for (; !q.empty();) {
auto [id, p] = q.top(); q.pop();
upd(id), fix(p);
}
for (auto i : vid) res[i] = v, bit1.clear(n - l[i] + 1), bit2.clear(r[i]);
// for (auto i : vid) std::cerr << i << '\n';
// for (auto [l, r] : seg) std::cerr << l << ' ' << r << '\n';
// std::cerr << "-------------\n";
}

void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i) {
int x;
std::cin >> x;
++cnt[x];
}
std::vector<int> vec;
for (int i = 1; i <= m; ++i) {
std::cin >> l[i] >> r[i];
vec.emplace_back(i);
}
for (int i = m; i; --i) sgt1.ins(1, 1, n, l[i], i), sgt2.ins(1, 1, n, r[i], i);
std::sort(vec.begin(), vec.end(), [&] (int i, int j) { return r[i] < r[j]; });
for (auto i : vec) sgt3.ins(1, 1, n, l[i], {-r[i], i});
list.init(m), bit1.clear(), bit2.clear();
std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
for (int i = n; i; --i) {
if (!cnt[i]) continue;
solve(i, cnt[i]);
}
for (int i = 1; i <= m; ++i) std::cout << res[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P11983 [JOIST 2025] 展览会 3 题解
https://sobaliuziao.github.io/2025/10/03/post/9b62653e.html
作者
Egg_laying_master
发布于
2025年10月3日
许可协议