完美的队列:
如果能求出每个加入的数在所有队列中都被移出去的时刻,那就可以很方便地计算答案了。
对序列分块,先考虑整块的,更新跨过整个块的区间答案,记 bi b i 表示队列要弹出当前的数需要的容量,那么每次操作实际上是对 bi b i 进行区间减法,注意到大部分时候是对整块打标记,如果不是整块打标记直接暴力就行了。当 所有 bi<0 b i < 0 的时候,就是所有东西都被弹出去的时候,可以利用双指针更新答案。
离散的块实际上也可以类似处理,预处理跨过整块的东西的前缀和,同样枚举每个位置和跨过它的区间,然后类似整块也可以双指针求出答案,这部分细节详见代码。
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define mp make_pair
#define pb push_back
#define Debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long LL;
typedef long double LD;
typedef unsigned int uint;
typedef pair <int, int> pii;
typedef unsigned long long uLL;
template <typename T> inline void Read(T &x) {
char c = getchar();
bool f = false;
for (x = 0; !isdigit(c); c = getchar()) {
if (c == '-') {
f = true;
}
}
for (; isdigit(c); c = getchar()) {
x = x * 10 + c - '0';
}
if (f) {
x = -x;
}
}
template <typename T> inline bool CheckMax(T &a, const T &b) {
return a < b ? a = b, true : false;
}
template <typename T> inline bool CheckMin(T &a, const T &b) {
return a > b ? a = b, true : false;
}
const int N = 100005;
const int B = 325;
const int inf = 0x3f3f3f3f;
int n, m, ans, a[N], b[N], f[N], l[N], r[N], x[N], all[N], sum[N], pos[N], qry[N];
vector <int> v[N];
int main() {
#ifdef wxh010910
freopen("d.in", "r", stdin);
#endif
Read(n), Read(m);
for (int i = 1; i <= n; ++i) {
Read(a[i]);
}
for (int i = 1; i <= m; ++i) {
Read(l[i]), Read(r[i]), Read(x[i]);
}
for (int L = 1, R; L <= n; L += B) {
R = min(L + B - 1, n);
int val = -inf, tag = 0, cnt = 0, tot = 0;
for (int i = L; i <= R; ++i) {
b[i] = a[i], CheckMax(val, b[i]);
}
for (int i = 1, j = 2; i <= m; ++i) {
if (i > 1 && val + tag <= 0) {
if (l[i] <= L && R <= r[i]) {
++tag;
} else if (l[i] <= R && L <= r[i]) {
val = -inf;
for (int k = L; k <= R; ++k) {
if (k >= l[i] && k <= r[i]) {
++b[k];
}
CheckMax(val, b[k]);
}
}
}
for (; val + tag > 0 && j <= m; ++j) {
if (l[j] <= L && R <= r[j]) {
--tag;
} else if (l[j] <= R && L <= r[j]) {
val = -inf;
for (int k = L; k <= R; ++k) {
if (k >= l[j] && k <= r[j]) {
--b[k];
}
CheckMax(val, b[k]);
}
}
}
sum[i] = sum[i - 1];
if (l[i] <= L && R <= r[i]) {
CheckMax(f[i], val + tag > 0 ? m + 1 : j - 1);
++sum[i], all[++tot] = i;
} else if (l[i] <= R && L <= r[i]) {
qry[++cnt] = i, pos[cnt] = tot;
}
}
for (int i = L; i <= R; ++i) {
for (int j = 1, k = 2, val = a[i]; j <= cnt; ++j) {
if (j > 1) {
val += sum[qry[j]] - sum[qry[j - 1]];
if (l[qry[j]] <= i && i <= r[qry[j]]) {
++val;
}
}
for (; val > 0 && k <= cnt; ++k) {
val -= sum[qry[k]] - sum[qry[k - 1]];
if (l[qry[k]] <= i && i <= r[qry[k]]) {
--val;
}
}
if (l[qry[j]] <= i && i <= r[qry[j]]) {
if (val > 0) {
CheckMax(f[qry[j]], sum[m] - sum[qry[cnt]] < val ? m + 1 : all[pos[cnt] + val]);
} else {
CheckMax(f[qry[j]], l[qry[k - 1]] <= i && i <= r[qry[k - 1]] ? val ? all[pos[k - 1] + val + 1] : qry[k - 1] : all[pos[k - 1] + val]);
}
}
}
}
}
memset(sum, 0, sizeof sum);
for (int i = 1; i <= m; ++i) {
if (!sum[x[i]]++) {
++ans;
}
v[f[i]].pb(x[i]);
for (auto x : v[i]) {
if (!--sum[x]) {
--ans;
}
}
printf("%d\n", ans);
}
#ifdef wxh010910
Debug("My Time: %.3lfms\n", (double)clock() / CLOCKS_PER_SEC);
#endif
return 0;
}
完美的集合:
注意到合法的关键点连成了一棵树,所以可以容斥,用 i i 合法的减去 和 pari p a r i 都合法的。
枚举之后将树处理出来,变成了一棵有根树,可以利用DFS序DP求出最大权值以及方案数。
剩下就是组合数取模的问题了,就是要快速求