洛谷 P13826 [Ynoi Easy Round 2026] 寒蝉鸣泣之时 题解

前言

传送门

毒瘤。

题意

给定 \(n\) 个边平行于坐标轴的平面矩形,以及正整数 \(m\),对 \(1\le m\cdot i\le n\) 的每个整数 \(i\) ,你需要计算出恰好被 \(m\cdot i\) 个矩形包含的区域的面积。

思路

矩形面积问题我们可以考虑扫描线。扫掉一维之后,我们需要思考一下用什么数据结构维护另一维。这个查询要求长得来者不善的样子,看起来用树形结构完全做不了,我们考虑分块。

用分块去维护什么呢?一个朴素的思路是我们去存储所有值 \(v\) 的出现次数 \(cnt_v\)。求对答案的贡献时,我们只需要看 \(v\) 是否是 \(m\) 的倍数,如果是的话,是多少倍,然后将 \(cnt_v\) 贡献给这个倍数的答案即可。

但是这样做显然太暴力了,每次算贡献都是 \(O(n)\) 的。我们还需要优化。

我们考虑一个值 \(v\) 会对倍数 \(t\) 产生贡献,当且仅当 \(v+tag=mt\)。这里 \(tag\) 就是对应块的增量标记。简单变形我们得到 \(t=\frac{v+tag}{m}\)。所以我们如果知道这一块 \(tag\) 的最小值、最大值的话,我们就可以得到一个 \(t\) 的上界和下界。设极差为 \(R\),那么单次计算贡献的复杂度就优化到了 \(O(\frac{R}{m}\sqrt{n})\)。由于扫描线的性质,\(tag\) 的变化量每次最多是 \(1\),我们考察某一个块,其过程中所有 \(R\) 的总和也就是 \(n\),所以对于每一块,整个过程中因计算贡献而产生的复杂度是 \(O(n)\)。这就可以接受了。所以我们对每一块额外维护一下 \(tag\) 的最值,以及每个 \(tag\) 的出现次数就可以。

然后就是 Ynoi 的传统艺能,我们来进行一个精细卡常。

先看空间。我们分析发现复杂度瓶颈在于,我们需要用二维数组 cnt[B][N] 维护每块中每个 \(tag\) 的出现次数。我们发现这个出现次数是 \(O(n)\) 级别的,且非负,用 int 存显然太浪费了。我们完全可以进行一个拆分,用 unsigned short 来存下低 \(16\) 位,用 unsigned char 来存剩下的高位。这样空间就是原来的 \(\frac{3}{4}\),计算一下发现是 OK 的。

再看时间。这里其实没什么大的优化,就是一些小细节。详见代码。

代码

#include<cstdio>
#include<vector>
#include<cmath>
#include<tuple>
#define ll long long
#define MIKU 0
using namespace std;

//快读快写。
#define SUBMIT
namespace Hatsune {
	#ifdef SUBMIT
	#define SIZE (1<<22)
	char in[SIZE], out[SIZE], *p1 = in, *p2 = in, *p3 = out;
	#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
	#define flush() (fwrite(out,1,p3-out,stdout),p3=out)
	#define putchar(ch) (p3==out+SIZE&&flush(),*p3++=(ch))
	class Flush { public:~Flush() { flush(); } }_;
	#endif
	namespace Miku {
		template<typename type>
		inline void read(type& x) {
			x = 0; bool flag(0); char ch = getchar();
			while(ch < '0' || ch>'9') flag ^= ch == '-', ch = getchar();
			while(ch >= '0' && ch <= '9') x = (x<<1)+(x<<3)+(ch^48), ch = getchar();
			flag ? x = -x : 0;
		}
		template<typename type, typename ...T>
		inline void read(type &x, T&...y) { read(x); read(y...); }
		/*- read & write -*/ 
		template<typename type>
		inline void write(type x, bool flag) {
			x < 0 ? x = -x, putchar('-') : 0; short Stack[50], top(0);
			do Stack[++top] = x % 10, x /= 10; while (x);
			while(top) putchar(Stack[top--] | 48);
			flag ? putchar('\n') : putchar(' ');
		}
	}
	#ifdef SUBMIT
	#undef SIZE
	#undef getchar
	#undef putchar
	#undef flush
	#endif
}using Hatsune::Miku::read; using Hatsune::Miku::write;

const int N = 3e5 + 5, B = 550, inf = 0x3f3f3f3f;
int n, m;
ll ans[N];
vector<tuple<int, int, int> > ed[2*N];

//拆分 int 存储。
unsigned short cnt_lo[B][N]; 
unsigned char cnt_hi[B][N];
//函数宏定义加速。
#define cnt_set(i, j) (cnt_lo[i][j] = cnt_hi[i][j] = 0)
#define cnt_add(i, j) (cnt_lo[i][j] ++, cnt_hi[i][j] += cnt_lo[i][j] ? 0 : 1)
#define cnt_get(i, j) (((int)cnt_hi[i][j]<<16) + (int)cnt_lo[i][j])

struct Block {
	int siz, tot;
	int op[B], ed[B], add[B], mx[B], mn[B], pos[N], val[N];
	void init() {
		siz = sqrt(n); tot = (n-1) / siz + 1;
		for(int i=1; i<=tot; i++) {
			op[i] = (i-1) * siz + 1;
			ed[i] = i * siz;
		} ed[tot] = n;
		for(int i=1; i<=n; i++) pos[i] = (i-1) / siz + 1;
	}
	void calc(int b) {
		if(mx[b] >= mn[b]) {
			for(int i=op[b]; i<=ed[b]; i++) {
				int v = val[i];
                //算一下 t 的上下界。
				int tl = max(1, (v+mn[b]-1)/m+1), tr = min(n/m, (v+mx[b])/m);
				for(int t=tl; t<=tr; t++) ans[t] += cnt_get(b, m*t-v);
			}
			for(int i=mn[b]; i<=mx[b]; i++) cnt_set(b, i);
			mx[b] = -inf; mn[b] = inf;
		}
	}
	void update(int l, int r, int v) {
		int L = pos[l], R = pos[r];
		if(L == R) {
			calc(L);
			for(int i=l; i<=r; i++) val[i] += v;
		} else {
			calc(L); calc(R);
			for(int i=l; i<=ed[L]; i++) val[i] += v;
			for(int i=L+1; i<R; i++) add[i] += v;
			for(int i=op[R]; i<=r; i++) val[i] += v;
		}
	}
} blc;

int main() {
	read(n, m); blc.init();
	for(int i=1; i<=n; i++) {
		int x_1, x_2, y_1, y_2; read(x_1, x_2, y_1, y_2);
		ed[x_1].emplace_back(y_1, y_2-1, 1);	//比 push_back 更快。
		ed[x_2].emplace_back(y_1, y_2-1, -1);
	}
	for(int i=1; i<=n; i++) {
		for(auto it : ed[i]) {
			int itl, itr, itv; tie(itl, itr, itv) = it;
			blc.update(itl, itr, itv);
		}
		for(int b=1; b<=blc.tot; b++) {
			cnt_add(b, blc.add[b]);
			blc.mn[b] = min(blc.mn[b], blc.add[b]);
			blc.mx[b] = max(blc.mx[b], blc.add[b]);
		}
	}
	for(int i=1; i<=blc.tot; i++) blc.calc(i);
	for(int i=1; i*m<=n; i++) write(ans[i], 1);
	return MIKU;
}
posted @ 2026-08-20 21:33  EtherealYz  阅读(3)  评论(0)    收藏  举报