「NOIP 模拟赛 20230705」序列删数问题

summarization

solution

首先发现,范围小的工具在删除某一数字时将更大数字包括进来的可能性越小,可以删除的数更多,所以在删除某一数字时应该尽可能选择范围较大的工具。

接下来我们考虑可删数(要删除的数)删除的顺序:

考虑要删掉每个数所允许的最大的工具的区间长度。现在假设有两个可删数:小数和大数。大数的两边某个位置被两个不可删去的比它更大的数占据了,于是它所允许的工具长度被这两个不可删去的数限制。然而,小数却有至少一边是被可删去的大数限制的。

此时我们考虑,若我们删去小数,则大数允许的区间长度会变小;若删去大数,则小数允许的区间长度会变大,变得更容易删去。因此我们应该先删除大的可删数。

于是,我们从大到小删除可删数。对于某一个可删数,我们使用能使用的最大的工具。若所有工具都无法使用,则输出 NO。如果所有可删数都被删除,则输出 YES

在代码实现上,对于找每个可删数左右离它最近的比它大的数,可以使用单调栈+二分。在单调栈时,无需考虑可删数及其变化,因为我们删除的顺序,比某个可删数大的可删数已经在它之前被删除了,而比它小的则不会影响边界。

对于删除,我们不必在删除某个数时真的把他删掉,因为这个数被删除只会影响接下来查询的区间长度,所以只需要在这个数上打个标记,在单调栈找到区间后将区间长度减掉这个区间中已经删除的数的个数即为这个区间真正的长度,用一个单点修改区间查询的数据结构即可。

code

CI N = 2e5, INF = 0x7fffffff; int T, n, m, a[N + 5], p[N + 5], l[N + 5], pp[N + 6]; set <int> s; int num[N + 5], c[N + 5], sk[N + 5], top = 0, L[N + 5], R[N + 5]; bool vis[N + 5];
int lowbit (int x) {return x & -x;}
void add (int id, int x) {for (; id <= n; id += lowbit (id)) c[id] += x;}
int sum (int id) {int s = 0; for (; id; id -= lowbit (id)) s += c[id]; return s;}
void D () {
	RI i, j; Mt (sk, 0); top = 0; for (i = 1; i <= n; ++ i) {
		if (! vis[i]) {W (top && sk[top] < a[i]) -- top; sk[++ top] = a[i];}
		else {int it = upper_bound (sk + 1, sk + top + 1, a[i], greater <int> ()) - sk; if (it == 1) L[i] = 1; else L[i] = pp[sk[it - 1]] + 1;}
	} Mt (sk, 0); top = 0; for (i = n; i >= 1; -- i) {
		if (! vis[i]) {W (top && sk[top] < a[i]) -- top; sk[++ top] = a[i];}
		else {int it = upper_bound (sk + 1, sk + top + 1, a[i], greater <int> ()) - sk; if (it == 1) R[i] = n; else R[i] = pp[sk[it - 1]] - 1;}
	} return ;
}
void init () {Mt (c, 0); s.clear (); Mt (num, 0); Mt (L, 0); Mt (R, 0); Mt (vis, 0);}
void get () {
	RI i, j; for (Read (n, m), i = 1; i <= n; ++ i) Read (a[i]), pp[a[i]] = i;  for (i = 1; i <= m; ++ i) Read (p[i]), vis[p[i]] = 1, p[i] = a[p[i]];
	sort (p + 1, p + m + 1); for (i = 1; i <= m; ++ i) Read (l[i]), s.insert (l[i]), ++ num[l[i]]; D (); for (i = m; i >= 1; -- i) {
		int id = pp[p[i]]; int len = (R[id] - L[id] + 1) - (sum (R[id]) - sum (L[id] - 1));
		if (num[len] > 0) {-- num[len]; if (num[len] == 0) s.erase (len);}
		else {
			set <int> :: iterator it = s.insert (len).first; if (it == s.begin ()) {printf ("NO\n"); return ;}
			-- it; int co = *it; -- num[co]; if (num[co] == 0) s.erase (co); s.erase (len);
		} add (id, 1);
	} printf ("YES\n");
}
int main () {
	RI i, j; Read (T); W (T --) {init (); get ();}
	return 0; 
}
posted @ 2023-07-06 17:43  ClapEcho233  阅读(14)  评论(0编辑  收藏  举报