JZOJ 6944. 【2020.01.07冬令营模拟】球(数学计算)

JZOJ 6944. 【2020.01.07冬令营模拟】球

题解

  • 发现数据很良心,区间坐标都是非负数,避免了更多的分类讨论。
  • 把区间拆开考虑, 发现在对角线 y = x y=x y=x两侧都满足每行/列分别单调,如果在某一侧框出一个矩形,可以用等差数列求和直接解决。
  • 同时对角线上的可以用平方和计算,但对角边旁构不成矩形的部分如何解决?
  • 又发现可以把对角线所经过部分框出一个正方形,分层后每一层都可以用一个最高次为三次的多项式求和计算,直接用立方和公式和平方和公式,那么这样剩下的部分都分居对角线两侧,用上述的方法。
  • 至于模数,看似不能用自然溢出,其实并不然,因为 2 64 2^{64} 264 2 63 2^{63} 263的倍数,所以可以自然溢出完再取模一次。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define md 9223372036854775808
ll read() {
	ll 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(ll x) {
	if(x < 0) x += md;
	if(x >= md) x -= md;
	int c[25], l = 0;
	if(x == 0) puts("0");
	else {
		while(x) c[++l] = x % 10, x /= 10;
		while(l) putchar(c[l] + 48), l--;
		puts("");
	}
}
ll sq(ll x) {
	ll t = x + 1, s = 2 * x + 1;
	if(x % 2 == 0) x /= 2; else if(t % 2 == 0) t /= 2; else s /= 2;
	if(x % 3 == 0) x /= 3; else if(t % 3 == 0) t /= 3; else s /= 3;
	return x * s * t;
}
ll sum(ll fi, ll d, ll n) {
	ll la = fi + d * (n - 1);
	if(n % 2 == 0) return (fi + la) * (n / 2);
	return (fi + la) / 2 * n;
}
ll ct(ll x) {
	if(x < 0) return 0;
	return sq(x) - sq(x / 2) * 4;
}
ll count(ll x, ll y) {
	return ct(y) - ct(x - 2);
}
ll squ(ll x) {
	if(x == 0) return 1;
	ll s = sum(1, 1, (x * 2 + 1) * (x * 2 + 1));
	ll r = sum(1, 1, x);
	ll t = r * r * 24 + r * 6 - sq(x) * 4 - x;
	return s - t;
}
ll solve(ll lx, ll rx, ll ly, ll ry) {
	if(lx > ry)  {
		return sum(count(lx * 2 - 1, rx * 2 - 1) + sum(lx - ry, 1, rx - lx + 1), rx - lx + 1, ry - ly + 1);
	}
	if(rx < ly) {
		return sum(count(ly * 2 + 1, ry * 2 + 1) - sum(ly - rx, 1, ry - ly + 1), -(ry - ly + 1), rx - lx + 1);
	}
	if(lx == ly) {
		if(rx == ry) {
			if(lx == 0) return squ(rx);
			return squ(rx) - squ(lx - 1) - solve(lx, rx, 0, ly - 1) - solve(0, lx - 1, ly, ry);
		}
		else if(rx < ry) {
			return solve(lx, rx, ly, rx) + solve(lx, rx, rx + 1, ry);
		}
		else if(ry < rx) {
			return solve(lx, ry, ly, ry) + solve(ry + 1, rx, ly, ry);
		}
	}
	else if(ly > lx) {
		if(rx == ry) {
			return solve(ly, rx, ly, ry) + solve(lx, ly - 1, ly, ry);
		}
		else if(rx < ry) {
			return solve(ly, rx, ly, rx) + solve(lx, ly - 1, ly, ry) + solve(ly, rx, rx + 1, ry);
		}
		else if(ry < rx) {
			return solve(ly, ry, ly, ry) + solve(lx, ly - 1, ly, ry) + solve(ry + 1, rx, ly, ry);
		}
	}
	else if(lx > ly) {
		if(rx == ry) {
			return solve(lx, rx, lx, ry) + solve(lx, rx, ly, lx - 1);
		}
		else if(rx < ry) {
			return solve(lx, rx, lx, rx) + solve(lx, rx, ly, lx - 1) + solve(lx, rx, rx + 1, ry);
		}
		else if(ry < rx) {
			return solve(lx, ry, lx, ry) + solve(lx, rx, ly, lx - 1) + solve(ry + 1, rx, lx, ry);
		}
	}
}
int main() {
	int Q = read();
	while(Q--) {
		ll lx = read(), rx = read(), ly = read(), ry = read();
		write(solve(lx, rx, ly, ry));
	}
	return 0;
}

自我小结

  • 比赛时想到了正解,但是因为一个字母写错和读入时没有开long long而爆零。
  • 值得注意,写代码时避免惯性思维,因“手熟”而写错。
  • 另外,内容比较对称的代码, 也可以对着检查,有错误容易查出。
原文地址:https://www.cnblogs.com/LZA119/p/14279478.html