题解:P9247 [集训队互测 2018] 完美的队列
思路:
@yuruilin2026 和@Hootime 两个神犇认为这是道水题并且三分钟切了。
看了时间以后直接考虑根号,时间复杂度就为一般的 \(O(n\sqrt{n\log n})\) 。
这道题突破点就在求每个 \(x\) 的贡献,不难看出,每个 \(x\) 的贡献取决于它在队列中存在的时间。具体的,记 \(x\) 的加入时间为 \(x_{in}\) ,\([l,r]\) 队列中最后一个 \(x\) 弹出的时间为 \(x_{out}\) ,那么这个 \(x\) 的贡献区间为 \([x_{in},x_{out}-1]\) 。
那么现在我们就需要求出最小的 \(x_{out}\) 。从第 \(x_{in}\) 刻开始,\([l,r]\) 中的每个位置 \(s\) 都至少被覆盖了 \(A_s\) 次(这样的话,就会把这次加入的 \(x\) 弹出)。现在开始考虑分块,每个块预处理为 \(f_{block,i}\) ,表示第 \(block\) 块中从 \(x_{in}\) 开始后最小的 \(x_{out}\) 。同样,每个块需要支持区间加和区间查询最值。
所以,对于每种颜色,我们对它的贡献区间取个并集,就能得到它对哪些时刻的答案有影响,而贡献区间总数是 \(O(n)\) 的,所以这部分是容易做的。
然后就是处理块,每个块的开头和结尾定义为 \(block_{start}\) 和 \(block_{end}\) ,若查询的 \([l,r]\) 中 \(l=block_{start},r=block_{end}\) 那么直接使用预处理的值,如果不是,那么就要处理其他散块的贡献,我们需要回答 \(O(m\sqrt n)\) 个询问,每个询问都是从第 \(x_{in}\) 时刻开始,位置 \(x\) 最小的 \(x_{out}\),使得在 \([x_{in},x_{out}]\) 中 \(x\) 被覆盖了至少 \(A_x\) 次。发现这里有序列维和时间维,考虑离线扫描线,维护每一个 \(x\) 在时间维上的信息,对序列进行扫描线,用线段树在 \(l\) 处给 \(x_{in}\) 时刻的增量加一,在 \(x_{out}+1\) 处给 \(x_{in}\) 时刻的增量减一,对于一个询问 \((x,x_{in})\),相当于在线段树的 \([x_{in},n]\) 这个后缀进行二分,得到最小的 \(x_{out}\) 。用线段树上二分容易实现,通过调整块长可以做到 \(n \sqrt{n \log n}\)。
Code:
#include <bits/stdc++.h>
using namespace std;
#define N 100000
int n, m, l, r, x, mx, tag, block, a[100010], c[100010], id[100010], L[100010], R[100010], rig[100010], d[100010];
int f[100010];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
s = (s << 3) + (s << 1) + (ch ^ 48);
ch = getchar();
}
return s * w;
}
//用于存储区间修改操作的左、右端点和修改值
struct opera {
int l, r, x; // l, r表示区间,x表示修改的值
} b[10*N+5];
//用于存储操作类型
struct Add {
int t, op; // t为操作的时间戳,op为操作类型
};
//用于存储区间
struct col {
int l, r;
};
// 排序用的比较器结构体
struct Cmp {
int r, id; // r表示区间右端点,id表示区间的标识
bool operator < (const Cmp &A) const {
return r < A.r; // 按照右端点r进行升序排序
}
};
//用于存储按右端点排序的区间
multiset <Cmp> s;
multiset <Cmp>::iterator itt; // multiset的迭代器
vector <int> ask[100010]; // 存储询问操作
vector <Cmp> jia[100010]; // 存储一些区间操作
vector <Add> ad[100010]; // 存储加法操作
vector <col> cun[100010]; // 存储区间数据
map <int, int> mp;
map <int, int>::iterator it; // map的迭代器
int max(int x, int y) {
return x > y ? x : y;
}
// modify函数用于区间修改操作,更新某些区间的值
void modify(int l, int r, int ql, int qr, int val) {
if (l > qr || r < ql) return ; // 如果当前区间与查询区间没有交集,返回
if (l <= ql && r >= qr) tag += val; // 如果当前区间完全包含查询区间,直接更新标记
else {
mx = -1e9; // 初始化最大值
for (int i = max(l, ql); i <= min(r, qr); i++) {
c[i] -= val; // 对指定区间进行更新
}
for (int i = ql; i <= qr; i++) {
mx = max(mx, c[i]); // 获取区间内的最大值
}
}
}
// 处理区间查询和更新
struct segment {
#define lc(x) x<<1 // 左子树节点索引
#define rc(x) x<<1|1 // 右子树节点索引
int lim, flag, now, d[400010]; // lim表示限制条件,flag表示状态标记,now表示当前处理的节点,d为区间的值数组
// 修改操作,用于更新区间的值
inline void modify(int k, int l, int r, int x, int y) {
if (l == r) {
d[k] += y; // 如果当前区间是一个单点,直接加上y
return ;
}
int mid = l + r >> 1; // 计算中间位置
if (x <= mid) modify(lc(k), l, mid, x, y); // 如果x在左子树范围内,递归左子树
else modify(rc(k), mid + 1, r, x, y); // 如果x在右子树范围内,递归右子树
d[k] = d[lc(k)] + d[rc(k)]; // 更新当前节点的值
}
// 查找操作,用于查找符合条件的节点
inline void find(int k, int l, int r) {
if (l == r) {
now = l; // 找到目标区间
return ;
}
int mid = l + r >> 1; // 计算中间位置
if (lim - d[lc(k)] <= 0) find(lc(k), l, mid); // 如果左子树满足条件,递归左子树
else {
lim -= d[lc(k)]; // 否则减少左子树的值,递归右子树
find(rc(k), mid + 1, r);
}
}
// 查询操作,用于查询区间内是否满足特定条件
inline void query(int k, int l, int r, int x, int y) {
if (x > y) return ; // 如果查询区间不合法,返回
if (x <= l && r <= y) { // 如果当前区间完全在查询区间内
if (flag) return ; // 如果已经标记过,返回
if (lim - d[k] <= 0) { // 如果当前区间满足条件,更新标记并查找
flag = 1;
find(k, l, r);
}
lim -= d[k]; // 减去当前区间的值
return ;
}
int mid = l + r >> 1; // 计算中间位置
if (x <= mid) query(lc(k), l, mid, x, y); // 查询左子树
if (y > mid) query(rc(k), mid + 1, r, x, y); // 查询右子树
}
} S;
void build() {
// 遍历每个元素,将其赋值并进行块编号
for (int i = 1; i <= n; i++) {
// 读取数据并存储到数组 a[i]
a[i] = read();
// 计算当前元素所属的块编号
id[i] = (i - 1) / block + 1;
// 如果当前元素所属的块与前一个元素不同,更新块的左端点 L
if (id[i] != id[i - 1]) L[id[i]] = i;
// 更新块的右端点 R
R[id[i]] = i;
}
}
// 处理范围修改和询问操作
void addandask() {
// 遍历每个块
for (int bl = 1; L[bl]; bl++) {
mx = tag = 0;
int ll = L[bl], rr = R[bl];
// 清空队列中右端点小于等于当前块右端点的元素
while (!s.empty() && (*s.begin()).r <= rr) s.erase(s.begin());
// 在块内遍历所有元素并更新最大值 mx 和每个元素的值 c[i]
for (int i = ll; i <= rr; i++) mx = max(mx, a[i]), c[i] = a[i];
// 遍历 b 数组中的每个操作进行修改和查询
for (int i = 1, j = 0; i <= m; i++) {
// 如果最大值大于标记值,则进行修改
while (j < m + 1 && mx > tag) {
j ++;
modify(b[j].l, b[j].r, ll, rr, 1); // 执行范围修改
}
// 记录修改操作后的结果
f[i] = j;
// 执行范围修改操作,值减 1
modify(b[i].l, b[i].r, ll, rr, -1);
}
// 处理最后一个操作
f[m + 1] = m + 1;
// 遍历集合 s 中的元素,更新 rig 数组
for (itt = s.begin(); itt != s.end(); itt++) {
rig[(*itt).id] = max(rig[(*itt).id], f[(*itt).id + 1] - 1);
}
// 更新当前块内的元素的状态
for (int i = ll; i <= rr; i++) {
for (int j = 0; j < jia[i].size(); j++) {
s.insert(jia[i][j]);
}
}
}
// 处理 b 数组中的操作
for (int i = 1; i <= m; i++) {
l = b[i].l, r = b[i].r, x = b[i].x;
// 如果操作在同一个块内,则直接处理
if (id[l] == id[r]) {
for (int j = l; j <= r; j++) {
ask[j].push_back(i);
}
} else {
// 处理跨块的情况,分别处理左右两部分
for (int j = l; id[j] == id[l]; j++) {
ask[j].push_back(i);
}
for (int j = r; id[j] == id[r]; j--) {
ask[j].push_back(i);
}
}
// 更新 ad 数组,记录修改操作
ad[l].push_back((Add) {i, 1});
ad[r + 1].push_back((Add) {i, -1});
}
}
// 执行所有查询和修改操作
void san() {
// 遍历每个元素,处理所有加法操作
for (int i = 1; i <= n; i++) {
// 遍历并执行所有加法操作
for (int j = 0; j < ad[i].size(); j++) {
int t = ad[i][j].t, op = ad[i][j].op;
S.modify(1, 1, m, t, op);
}
// 遍历所有询问操作并处理
for (int j = 0; j < ask[i].size(); j++) {
int fir = ask[i][j];
S.lim = a[i], S.flag = 0, S.now = m + 1;
S.query(1, 1, m, fir + 1, m); // 执行查询
rig[fir] = max(rig[fir], S.now - 1); // 更新结果
}
}
// 将结果存储到 cun 数组中
for (int i = 1; i <= m; i++) {
cun[b[i].x].push_back((col) {i, rig[i]});
}
// 对每个 x 对应的操作进行区间统计
for (int i = 1; i <= N; i++) {
if (!cun[i].size()) continue;
mp.clear();
// 对每个元素进行操作
for (int j = 0; j < cun[i].size(); j++) {
mp[cun[i][j].l]++, mp[cun[i][j].r + 1]--;
}
// 进行区间修改
int lst = 0, now = 0;
mp[m + 1] = 0;
for (it = mp.begin(); it != mp.end(); it++) {
if (now) d[lst]++, d[it->first]--;
now += it->second;
lst = it->first;
}
}
}
// 主函数,程序的入口
int main() {
// 读取 n 和 m 的值
n = read(), m = read();
// 初始化块大小
block = 190;
// 执行初始化操作
build();
// 读取 b 数组中的操作
for (int i = 1; i <= m; i++) {
b[i].l = read(), b[i].r = read(), b[i].x = read();
// 记录每个操作对块内元素的影响
jia[b[i].l].push_back((Cmp) {b[i].r, i});
}
// 执行添加和询问操作
addandask();
// 执行结果处理
san();
// 输出最终的结果
for (int i = 1; i <= m; i++) {
d[i] += d[i - 1]; // 更新累积结果
printf("%d\n", d[i]); // 输出每个查询的结果
}
return 0; // 程序完美结束
}//这个代码长度刚好6.66KB
纯净版:
#include <bits/stdc++.h>
using namespace std;
#define N 100000
int n, m, l, r, x, mx, tag, block, a[100010], c[100010], id[100010], L[100010], R[100010], rig[100010], d[100010];
int f[100010];
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
s = (s << 3) + (s << 1) + (ch ^ 48);
ch = getchar();
}
return s * w;
}
struct opera {
int l, r, x;
} b[10*N+5];
struct Add {
int t, op;
};
struct col {
int l, r;
};
struct Cmp {
int r, id;
bool operator < (const Cmp &A) const {
return r < A.r;
}
};
multiset <Cmp> s;
multiset <Cmp> :: iterator itt;
vector <int> ask[100010];
vector <Cmp> jia[100010];
vector <Add> ad[100010];
vector <col> cun[100010];
map <int, int> mp;
map <int, int> :: iterator it;
inline int max(int x, int y) {
return x > y ? x : y;
}
void modify(int l, int r, int ql, int qr, int val) {
if (l > qr || r < ql) return ;
if (l <= ql && r >= qr) tag += val;
else {
mx = -1e9;
for (int i = max(l, ql); i <= min(r, qr); i++) {
c[i] -= val;
}
for (int i = ql; i <= qr; i++) {
mx = max(mx, c[i]);
}
}
}
struct segment {
#define lc(x) x<<1
#define rc(x) x<<1|1
int lim, flag, now, d[400010];
inline void modify(int k, int l, int r, int x, int y) {
if (l == r) {
d[k] += y;
return ;
}
int mid = l + r >> 1;
if (x <= mid) modify(lc(k), l, mid, x, y);
else modify(rc(k), mid + 1, r, x, y);
d[k] = d[lc(k)] + d[rc(k)];
}
inline void find(int k, int l, int r) {
if (l == r) {
now = l;
return ;
}
int mid = l + r >> 1;
if (lim - d[lc(k)] <= 0) find(lc(k), l, mid);
else {
lim -= d[lc(k)];
find(rc(k), mid + 1, r);
}
}
inline void query(int k, int l, int r, int x, int y) {
if (x > y) return ;
if (x <= l && r <= y) {
if (flag) return ;
if (lim - d[k] <= 0) {
flag = 1;
find(k, l, r);
}
lim -= d[k];
return ;
}
int mid = l + r >> 1;
if (x <= mid) query(lc(k), l, mid, x, y);
if (y > mid) query(rc(k), mid + 1, r, x, y);
}
} S;
void build() {
for (int i = 1; i <= n; i++) {
a[i] = read();
id[i] = (i - 1) / block + 1;
if (id[i] != id[i - 1]) L[id[i]] = i;
R[id[i]] = i;
}
}
void addandask() {
for (int bl = 1; L[bl]; bl++) {
mx = tag = 0;
int ll = L[bl], rr = R[bl];
while (!s.empty() && (*s.begin()).r <= rr) s.erase(s.begin());
for (int i = ll; i <= rr; i++) mx = max(mx, a[i]), c[i] = a[i];
for (int i = 1, j = 0; i <= m; i++) {
while (j < m + 1 && mx > tag) {
j ++;
modify(b[j].l, b[j].r, ll, rr, 1);
}
f[i] = j;
modify(b[i].l, b[i].r, ll, rr, -1);
}
f[m + 1] = m + 1;
for (itt = s.begin(); itt != s.end(); itt++) {
rig[(*itt).id] = max(rig[(*itt).id], f[(*itt).id + 1] - 1);
}
for (int i = ll; i <= rr; i++) {
for (int j = 0; j < jia[i].size(); j++) {
s.insert(jia[i][j]);
}
}
}
for (int i = 1; i <= m; i++) {
l = b[i].l, r = b[i].r, x = b[i].x;
if (id[l] == id[r]) {
for (int j = l; j <= r; j++) {
ask[j].push_back(i);
}
} else {
for (int j = l; id[j] == id[l]; j++) {
ask[j].push_back(i);
}
for (int j = r; id[j] == id[r]; j--) {
ask[j].push_back(i);
}
}
ad[l].push_back((Add) {
i, 1
});
ad[r + 1].push_back((Add) {
i, -1
});
}
}
void san() {
for (int i = 1; i <= n; i++) {
for (int j = 0; j < ad[i].size(); j++) {
int t = ad[i][j].t, op = ad[i][j].op;
S.modify(1, 1, m, t, op);
}
for (int j = 0; j < ask[i].size(); j++) {
int fir = ask[i][j];
S.lim = a[i], S.flag = 0, S.now = m + 1;
S.query(1, 1, m, fir + 1, m);
rig[fir] = max(rig[fir], S.now - 1);
}
}
for (int i = 1; i <= m; i++) {
cun[b[i].x].push_back((col) {
i, rig[i]
});
}
for (int i = 1; i <= N; i++) {
if (!cun[i].size()) continue;
mp.clear();
for (int j = 0; j < cun[i].size(); j++) {
mp[cun[i][j].l] ++, mp[cun[i][j].r + 1] --;
}
int lst = 0, now = 0;
mp[m + 1] = 0;
for (it = mp.begin(); it != mp.end(); it++) {
if (now) d[lst] ++, d[it->first] --;
now += it->second;
lst = it->first;
}
}
}
int main() {
n = read(), m = read();
block = 190;
build();
for (int i = 1; i <= m; i++) {
b[i].l = read(), b[i].r = read(), b[i].x = read();
jia[b[i].l].push_back((Cmp) {
b[i].r, i
});
}
addandask();
san();
for (int i = 1; i <= m; i++) {
d[i] += d[i - 1];
printf ("%d\n", d[i]);
}
return 0;
}

浙公网安备 33010602011771号