AT_wtf22_day1_d Welcome to Tokyo! 题解
非常有意思的题目。
思路
考虑如何统计答案。
我们设 \(a_i\) 为是否在第 \(i\) 天举办宴会,\(b_i\) 为第 \(i\) 个人是否参加过宴会。
那么有:
\[\left\{\begin{matrix}
a_i\le 1\\
b_i\le 1\\
b_i\le \sum_{j=l_i}^{r_i}a_j\\
\sum_{i=1}^n a_i\le k
\end{matrix}\right.\]
这是一个线性规划形式,虽然我感觉初看原题的时候很难想到要写成这样的线性规划形式。
我们要统计的是:
\[\begin{aligned}
\max \sum_{i=1}^m b_i
\end{aligned}\]
整理一下:
\[\max\sum_{i=1}^m b_i\\
\left\{\begin{matrix}
a_i-1\le 0\\
b_i-1\le 0\\
b_i-\sum_{j=l_i}^{r_i}a_j\le 0\\
\sum_{i=1}^n a_i-k\le0\\
a_i,b_i\ge 0
\end{matrix}\right.
\]
由于原问题并不是很好做,我们可以想到线性规划对偶来看一看。
可以对限制加上一些乘子变量放到式子中。
\[\sum_{i=1}^mb_i+\sum_{i=1}^nx_i(a_i-1)+\sum_{i=1}^my_i(b_i-1)+\sum_{i=1}^mz_i(b_i-\sum_{j=l_i}^{r_i}a_j)+(\sum_{i=1}^na_i-k)u
\]
然后以 \(a_i,b_i\) 为主元。
\[\sum_{i=1}^m(1+y_i+z_i)b_i+\sum_{i=1}^n(x_i-\sum_{j=l_i}^{r_i}z_j+u)a_i-\sum_{i=1}^nx_i-\sum_{i=1}^my_i-ku
\]
依据 \(a_i,b_i\ge 0\) 写出限制。
\[\min-\sum_{i=1}^n x_i-\sum_{i=1}^my_i-ku\\
\left\{\begin{matrix}
1+y_i+z_i\le 0\\
x_i-\sum_{j=l_i}^{r_i}z_j+u\le 0\\
x_i,y_i,z_i,u\le 0
\end{matrix}\right.
\]
整理一下。
\[\min\sum_{i=1}^n x_i+\sum_{i=1}^my_i+ku\\
\left\{\begin{matrix}
y_i+z_i\ge 1\\
x_i+u\ge \sum_{j=l_i}^{r_i}z_j\\
x_i,y_i,z_i,u\ge 0
\end{matrix}\right.
\]
容易发现 \(y_i+z_i\) 肯定等于 \(1\) 时最优。
而使用 \(x_i\) 去抵消 \(z_j\) 的影响肯定不优,所以一定有 \(x_i=0\)。
那么问题又简化成:
\[\min m-\sum_{i=1}^mz_i+ku\\
\left\{\begin{matrix}
z_i\le 1\\
u\ge \sum_{j=l_i}^{r_i}z_j\\
z_i,u\ge 0
\end{matrix}\right.
\]
它的意义是什么。
我们需要挑选出一个最大的线段集,使得每个点被覆盖的次数小于等于 \(u\)。
不妨设答案为 \(f_u\)。
其中,\(f_1\) 是经典贪心问题,而这个贪心结论放在 \(f_i\) 同样成立,也就是不断的选取合法的右端点最小的线段。
并且又有结论是必定会有 \(f_i\) 是 \(f_{i+1}\) 的子集,所以我们可以增量构造。
使用线段树模拟即可。
时间复杂度:\(O(m\log n+n)\)。
Code
/*
! 如果没有天赋,那就一直重复
! Created: 2024/06/26 15:23:52
*/
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)
const int N = 1e6 + 10;
int n, m, l[N], r[N], d[N], w[N], s[N];
int v[N << 1];
int g[N << 1];
list<int> t[N << 1];
inline void upd(int p, int l, int r, int k, int x) {
t[p].push_back(x);
if (l == r) return;
int mid = (l + r) >> 1;
if (mid >= k) upd(mid<<1, l, mid, k, x);
if (mid < k) upd(mid<<1|1, mid + 1, r, k, x);
}
inline void ckm(int&x, int y) {
if (x == 0) x = y; else if (y && r[x] > r[y]) x = y;
}
inline void pdo(int p, int mid) {
if (g[p]) {
g[mid<<1] += g[p], g[mid<<1|1] += g[p];
v[mid<<1] += g[p], v[mid<<1|1] += g[p];
g[p] = 0;
}
}
inline void add(int p, int l, int r, int L, int R) {
if (L <= l && r <= R) return g[p]++, v[p]++, void();
int mid = (l + r) >> 1; pdo(p, mid);
if (mid >= L) add(mid<<1, l, mid, L, R);
if (mid < R) add(mid<<1|1, mid + 1, r, L, R);
v[p] = max(v[mid<<1], v[mid<<1|1]);
}
inline int ask(int p, int l, int r, int L, int R) {
while (t[p].empty() == 0 && w[t[p].front()]) t[p].pop_front();
if (t[p].empty()) return 0;
if (L <= l && r <= R) return t[p].front();
int mid = (l + r) >> 1, num = 0;
if (mid >= L) ckm(num, ask(mid<<1, l, mid, L, R));
if (mid < R) ckm(num, ask(mid<<1|1, mid + 1, r, L, R));
return num;
}
inline int ask(int p, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1; pdo(p, mid);
return v[mid<<1|1] == k ? ask(mid<<1|1, mid + 1, r, k) : ask(mid<<1, l, mid, k);
}
inline int get(int i, int j) {
return m - s[i] + j * i;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
fro(i, 1, m) {
cin >> l[i] >> r[i];
}
iota(d + 1, d + m + 1, 1);
sort(d + 1, d + m + 1, [&](int x, int y) {
return r[x] < r[y];
});
fro(i, 1, m) upd(1, 1, n, l[d[i]], d[i]);
fro(i, 1, m) {
s[i] = s[i - 1];
if (s[i] != m) {
int las = 1;
while (s[i] < m && las <= n) {
int x = ask(1, 1, n, las, n);
if (x == 0) break;
w[x] = 1, s[i]++, add(1, 1, n, l[x], r[x]);
if (v[1] == i) las = ask(1, 1, n, i) + 1;
}
}
}
int it = m;
fro(i, 1, n) {
while (it && get(it, i) > get(it - 1, i)) it--;
cout << get(it, i) << "\n";
}
return 0;
}