LuoguP7505 「Wdsr-2.5」小小的埴轮兵团 题解

Content

给出一个范围为 \([-k,k]\) 的数轴,数轴上有 \(n\) 个点,第 \(i\) 个点的位置为 \(a_i\)。有 \(m\) 次操作,有且仅有以下三种:

  • 1 x:所有点往右移动 \(x\) 个单位。
  • 2 x:所有点往左移动 \(x\) 个单位。
  • 3:求出还在数轴范围以内的点的个数。

如果在某个操作中有点移出数轴范围了,那么尽管后面的操作能够把它拉回数轴上来,它也不能够回到数轴上面来了。

数据范围:\(1\leqslant n,m\leqslant 3\times 10^5\)\(1\leqslant k,x\leqslant 2\times 10^9\)\(-k\leqslant a_i\leqslant k\)

Solution

方法非常地清晰,可以说是整场比赛最良心的一道题目。

首先,为了保证下面的解法合理,我们先将所有的点按照 \(a_i\) 升序排序(注意!题目中并没有保证这一点!)。

然后我们不难发现,还在数轴范围上的点一定是在某个区间上的一个整体。所以我们考虑存储左边界和右边界。还需要将所有点往右移动的距离记为 \(dis\)

对于操作 \(1\),我们只需要将 \(dis\leftarrow dis+x\),然后再从右往左遍历所有编号在 \([l,r]\) 范围内的点:

  • 如果当前遍历的点在之前已经不在数轴范围内了,那么停止遍历。
  • 否则,如果当前遍历的点 \(a_i+dis>k\),也就是超过了右边界,那么将它标记一下,并且将用来统计在该次操作中被移除数轴范围的点的个数的计数器 \(cnt\)\(1\)
  • 否则,就说明当前遍历的点在数轴范围了,不用再往左遍历了,停止遍历。
  • 操作完以后记得将右边界 \(r\) 减去统计的在该次操作中被移除数轴范围的点的个数,即 \(r\leftarrow r-cnt\)

对于操作 \(2\),我们只需要将 \(dis\leftarrow dis-x\),然后再从左往右遍历所有编号在 \([l,r]\) 范围内的点:

  • 如果当前遍历的点在之前已经不在数轴范围内了,那么停止遍历。
  • 否则,如果当前遍历的点 \(a_i+dis<-k\),也就是超过了左边界,那么将它标记一下,并且将用来统计在该次操作中被移除数轴范围的点的个数的计数器 \(cnt\)\(1\)
  • 否则,就说明当前遍历的点在数轴范围了,不用再往右遍历了,停止遍历。
  • 操作完以后记得将左边界 \(l\) 加上统计的在该次操作中被移除数轴范围的点的个数,即 \(l\leftarrow l+cnt\)

对于操作 \(3\),我们需要特判一下是否有 \(r<l\),如果是的话说明已经没有点在数轴上面了,答案是 \(0\),否则答案就是 \(r-l+1\)

最后注意一点,上面的操作中 \(a_i+dis\) 可能会超出 int 的范围 \([-2^{31},2^{31})\),会导致你 WA 50,因此要开 long long

Code

const int N = 3e5 + 7;
int n, m, k, L, R, l, r, x, vis[N];
ll dis, a[N];

int main() {
	n = Rint, m = Rint, k = Rint, L = -k, R = k, l = 1, r = n;
	F(int, i, 1, n) a[i] = Rint; sort(a + 1, a + n + 1);
	while(m--) {
		int op = Rint, out = 0;
		if(op == 1) {
			dis += (x = Rint);
			R(int, i, r, l) {
				if(vis[i]) break;
				else if(a[i] + dis > R) vis[i] = 1, out++;
				else break;
			}
			r -= out;
		} else if(op == 2) {
			dis -= (x = Rint);
			F(int, i, l, r) {
				if(vis[i]) break;
				else if(a[i] + dis < L) vis[i] = 1, out++;
				else break;
			}
			l += out;
		} else println(r < l ? 0 : r - l + 1);
	}
    return 0;
}
posted @ 2021-12-15 22:12  Eason_AC  阅读(313)  评论(0)    收藏  举报