JZOJ 7049. 2021.04.07【2021省赛模拟】生命游戏(拉格朗日插值+矩形面积并)

JZOJ 7049. 2021.04.07【2021省赛模拟】生命游戏

题目大意

  • 二维平面网格中初始有 n n n个点,每一时刻某一位置存在点当且仅当上一时刻相邻四格中存在点, Q Q Q次询问,求时刻 0 0 0到时刻 t t t平面上存在的点的总和。
  • n ≤ 100 , Q ≤ 1 0 6 n\le100, Q\le10^6 n100Q106

题解

  • 当只有一个点时,手玩一下可以发现,每一时刻 t t t存在的点数为 ( t + 1 ) 2 (t+1)^2 (t+1)2
  • 当有多个点时,坐标 ( x i + y i ) (x_i+y_i) (xi+yi)奇偶性不同的点之间互不影响。
  • 其中,有两个点且 ( x i + y i ) (x_i+y_i) (xi+yi)奇偶性相同时,两点扩展范围相交前互不影响,易得相交时间为 ∣ x i − x j ∣ + ∣ y i − y j ∣ 2 \frac{|x_i-x_j|+|y_i-y_j|}{2} 2xixj+yiyj,即曼哈顿距离的一半,相交后它们重叠部分随时间增长为依次为 1 × r 1\times r 1×r 2 × ( r + 1 ) 2\times (r+1) 2×(r+1) 3 × ( r + 2 ) 3\times(r+2) 3×(r+2)……其中 r = m i n ( ∣ x i − x j ∣ , ∣ y i − y j ∣ ) r=min(|x_i-x_j|,|y_i-y_j|) r=min(xixj,yiyj)
  • 综上当 n = 1 n=1 n=1 2 2 2时,每一时刻 t t t的点数都是关于 t t t的二次多项式,从 0 0 0 t t t求和后为关于 t t t的三次多项式,但 n = 2 n=2 n=2相交前后的多项式系数不同。
  • 那么,当 n > 2 n>2 n>2时,若先求出两两相交时刻作为“关键时刻”,相邻两个关键时刻之间任一时刻 t t t的答案一定可以表示为同一个关于 t t t的三次多项式,只需要 4 4 4个位置的值,则可以把系数解出来或求答案时用拉格朗日插值求出来。
  • 现在的问题是需要求 4 4 4个位置的答案,方便起见,可以直接求最前的 4 4 4个时刻,用上一个时刻的答案加上当前时刻的总数即为当前时刻的答案。
  • 发现求每一时刻 t t t的总数即为求若干个边长为 t + 1 t+1 t+1的正方形面积并,它们的左下角坐标为 ( x i + y i 2 , x i − y i 2 ) (\frac{x_i+y_i}{2},\frac{x_i-y_i}{2}) (2xi+yi,2xiyi),转换坐标后用线段树即可。
  • 时间复杂度为 O ( n 3 log ⁡ 2 n ) O(n^3\log_2n) O(n3log2n),实现的不好可能需要一些优化:如把坐标离散化,而不使用动态开点;拉格朗日差值时预处理好分母之积的逆元。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define md 998244353
#define N 110
struct node {
	int x, y, l, r, id;
}a[2][N], b[2][N], h[N * 2];
struct qu {
	int x, l, r, c;
}q[N * 2];
struct {
	int s, t, l, r;
}f[N * 16];
int n, sum;
int c[2], st[N * N], ti[2][N * N], si[2], fx[2][N * N][5];;
ll inv[2][N * N][5], he[N * 2], le[N * 4], ri[N * 4];
int dis(int o, int x, int y) {
	return abs(a[o][x].x - a[o][y].x) + abs(a[o][x].y - a[o][y].y);
}
int cmp(node x, node y) {
	return x.x < y.x;
}
ll ksm(ll x, ll y) {
	if(!y) return 1;
	ll l = ksm(x, y / 2);
	if(y % 2) return l * l % md * x % md;
	return l * l % md;
}
void ins(int v, int l, int r, int x, int y, int c) {
	if(x <= l && r <= y) f[v].t += c;
	else {
		int mid = (ll)((ll)l + r) / 2;
		if(x <= mid) {
			if(!f[v].l)  f[v].l = ++sum, f[sum].l = f[sum].r = f[sum].t = 0;
			ins(f[v].l, l, mid, x, y, c);
		}
		if(y > mid) {
			if(!f[v].r) f[v].r = ++sum, f[sum].l = f[sum].r = f[sum].t = 0;
			ins(f[v].r, mid + 1, r, x, y, c);
		}
	}
	if(f[v].t) f[v].s = ri[r] - le[l] + 1; else f[v].s = f[f[v].l].s + f[f[v].r].s;
}
ll count(int o, int T) {
	if(!c[o]) return 0;
	int tot = 0;
	for(int i = 1; i <= c[o]; i++) {
		h[++tot].x = b[o][i].y, h[tot].y = 1, h[tot].id = i;
		h[++tot].x = b[o][i].y + T, h[tot].y = 2, h[tot].id = i;
	}
	sort(h + 1, h + tot + 1, cmp);
	
	int t0 = 0;
	for(int i = 1; i <= tot; i++) {
		if(i == 1 || h[i].x > h[i - 1].x) t0++;
		he[t0] = h[i].x;
		if(h[i].y == 1) b[o][h[i].id].l = t0 * 2 - 1; 
		else if(h[i].y == 2) b[o][h[i].id].r = t0 * 2 - 1;
	}
	
	for(int i = 1; i <= t0 * 2 - 1; i++) {
		if(i % 2 == 0) ri[i] = he[i / 2 + 1] - 1, le[i] = he[i / 2] + 1;
		else le[i] = ri[i] = he[i / 2 + 1];
	}
	
	int j = 1, qs = 0;
	for(int i = 1; i <= c[o]; i++) {
		while(b[o][j].x + T + 1 <= b[o][i].x && j <= c[o]) {
			q[++qs].x = b[o][j].x + T + 1, q[qs].c = -1;
			q[qs].l = b[o][j].l, q[qs].r = b[o][j].r;
			j++;
		}
		q[++qs].x = b[o][i].x, q[qs].c = 1;
		q[qs].l = b[o][i].l, q[qs].r = b[o][i].r;
	}
	while(j <= c[o]) {
		q[++qs].x = b[o][j].x + T + 1, q[qs].c = -1;
		q[qs].l = b[o][j].l, q[qs].r = b[o][j].r;
		j++;
	}
	ll ans = 0;
	sum = 1, f[sum].l = f[sum].r = f[sum].s = f[sum].t = 0;
	
	for(int i = 1; i <= qs; i++) {
		ll t = f[1].s;
		if(i > 1) ans = (ans + t * (q[i].x - q[i - 1].x)) % md;
		ins(1, 1, t0 * 2 - 1, q[i].l, q[i].r, q[i].c);
	}
	return ans;
}
ll cs(int o, int T) {
	if(!c[o]) return 0;
	int l = 1, r = si[o], ts;
	while(l <= r) {
		int mid = (l + r) / 2;
		if(T >= ti[o][mid]) ts = mid, l = mid + 1; else r = mid - 1;
	}
	if(T - ti[o][ts] < 4) return fx[o][ts][T - ti[o][ts] + 1];
	
	ll sum = 0;
	for(int i = 1; i <= 4; i++) {
		ll s0 = 1;
		for(int j = 1; j <= 4; j++) if(j != i) s0 = s0 * (T - (ti[o][ts] + j - 1)) % md;
		sum = (sum + s0 * inv[o][ts][i]) % md;
	}
	return sum;
}
int read() {
	int s = 0;
	char x = getchar();
	while(x < '0' || x > '9') x = getchar();
	while(x >= '0' && x <= '9') s = s * 10 + x - 48, x = getchar();
	return s;
}
void write(int t) {
	if(t == 0) puts("0");
	else {
		int bi[10]; bi[0] = 0;
		while(t) bi[++bi[0]] = t % 10, t /= 10;
		while(bi[0]) putchar(bi[bi[0]] + 48), bi[0]--;
		puts("");	
	}
}
int main() {
	int Q, o, i, j, x, y;
	n = read(), Q = read();
	for(i = 1; i <= n; i++) {
		x = read(), y = read();
		o = (x + y) % 2;
		c[o]++;
		a[o][c[o]].x = x, a[o][c[o]].y = y;
	}
	for(o = 0; o < 1; o++) {
		int mix = 1e9, miy = 1e9;
		for(i = 1; i <= c[o]; i++) {
			b[o][i].x = (a[o][i].x + a[o][i].y) / 2;
			b[o][i].y = (a[o][i].x - a[o][i].y) / 2;
			mix = min(mix, b[o][i].x);
			miy = min(miy, b[o][i].y);
		}
		int mx = 0;
		for(i = 1; i <= c[o]; i++) b[o][i].x -= mix - 1, b[o][i].y -= miy - 1, mx = max(b[o][i].x, mx), mx = max(b[o][i].y, mx);	
		sort(b[o] + 1, b[o] + c[o] + 1, cmp);
	}
	for(o = 0; o < 2; o++) {
		st[0] = 0;
		for(i = 1; i < c[o]; i++) 
			for(j = i + 1; j <= c[o]; j++) st[++st[0]] = dis(o, i, j) / 2;
		sort(st + 1, st + st[0] + 1);
		si[o] = 1;
		ti[o][1] = 0;
		for(i = 1; i <= st[0]; i++) {
			if(i > 1 && st[i] == st[i - 1]) continue;
			ti[o][++si[o]] = st[i];
		}
		ll la = 0;
		for(i = 1; i <= si[o]; i++) {
			if(ti[o][i] == 0) la = 0; else la = cs(o, ti[o][i] - 1);
			for(j = ti[o][i]; j < ti[o][i] + 4 && (i == si[o] || j < ti[o][i + 1]); j++) {
				fx[o][i][++fx[o][i][0]] = (la + count(o, j)) % md;
				la = fx[o][i][fx[o][i][0]];
			}
			if(fx[o][i][0] == 4) {
				for(int ii = 1; ii <= 4; ii++) {
					inv[o][i][ii] = 1;
					for(int jj = 1; jj <= 4; jj++) if(jj != ii) inv[o][i][ii] = inv[o][i][ii] * (ii - jj + md) % md;
					inv[o][i][ii] = fx[o][i][ii] * ksm(inv[o][i][ii], md - 2) % md;
				}	
			}
		}
	}
	while(Q--) {
		int t = read();
		write((cs(0, t) + cs(1, t)) % md);
	}
	return 0;
}

自我小结

  • 实现的过程中思路比较清晰, 实现的也比较快,但因动态开点超时想办法改了很久,最后不得不用离散化才过。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值