NOIP模拟测试7

考爆了,爆零,GG.

T1:方程的解

一眼Exgcd,求出最小解,再求出所有解。

然而Exgcd忘了,面向数据范围编程,特判少了,40分滚粗。

这题要特判的多了。。。

  1. 要注意特判0
  2. 要注意特判符号
  3. 要注意特判有无正整数解

大体来说就是这3条,关于符号的问题,Exgcd肯定要正数,可以先把负数标记一下,取相反数,Exgcd后再改回来。

关于Exgcd,有必要再证明一下。。。

已知不定方程\(ax + by = c\),首先由某不记得名字的推论(裴蜀定理),它有解的必要条件为 \((a, b) | c\).

我们设\(g = (a, b)\),先求解\(ax + by = g\).

因为\((a, b) = (b, a % b), a \% b = a - b * \lfloor \frac{a}{b} \rfloor\),所以\(bx + (a - b * \lfloor \frac{a}{b} \rfloor)y = g\).即:\(ay + b(x - \lfloor \frac{a}{b} \rfloor y) = g\).

可得原方程一组解 \(x_1 = y, y_1 = x - \lfloor \frac{a}{b} \rfloor y\).

\(c = kg\),我们令该不定方程两边同乘\(k\)\(ax_0 + by_0 = c\), 其中\(x_0 = kx_1, y_0 = ky_1\).

这是原方程的一组特解。要获得同解,我们让\(x + \Delta x, y + \Delta y\). \(a(x_0 + \Delta x) + b (y_0 + \Delta y) = c\).为使方程平衡,\(a \Delta x = b \Delta y\).

所以\(\Delta x = \frac{b}{g}, \Delta y = \frac{a}{g}\).

原方程同解为$x = x_0 + t \frac{b}{g}, y = y_0 - t \frac{a}{g}, t \in Z $.

求出刚好大于0的y,求出刚好大于0的x对应的最大y,差值除以步长\(\frac{a}{g}\)再+1就是答案。

本题过于难调,\(Have\ fun\ debugging!\) (逃

#include <bits/stdc++.h>
#define ll long long

ll Exgcd(ll a, ll b, ll &x, ll &y) {
	if (b == 0) {
		x = 1, y = 0; return a;
	}
	ll r = Exgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return r;
}

ll Solve(ll a, ll b, ll c) {
	bool opa = 0, opb = 0;
	if (a == 0 && b == 0) return c == 0 ? -1 : 0;
	if (a == 0) return (c == 0 || (c % b == 0 && c / b > 0)) ? -1 : 0;
	if (b == 0) return (c == 0 || (c % a == 0 && c / a > 0)) ? -1 : 0;
	if (c < 0) a = -a, b = - b, c = -c;
	if (a < 0) a = -a, opa = 1;
	if (b < 0) b = -b, opb = 1;
	ll x, y;
	ll g = Exgcd(a, b, x, y);
	if (c % g != 0) return 0;
	ll t = c / g; 		x *= t, y *= t, a /= g, b /= g, c = t;
	if (opa) a = -a, x = -x;
	if (opb) b = -b, y = -y;
	if (a < 0) a = -a, b = -b, c = -c;
	if (a * b < 0) return -1;
	x %= b;
	while (x <= 0) x += b;
	y = (c - a * x) / b;
	if (y < 0) return 0;
	ll y_min = y % a;
	while (y_min <= 0) y_min += a;
	return y_min <= y ? (y - y_min) / a + 1 : 0;
}

signed main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		ll a, b, c;
		scanf("%lld%lld%lld", &a, &b, &c);
		ll ans = Solve(a, b, c);
		if (ans == -1 || ans > 65535) puts("ZenMeZheMeDuo");
		else printf("%lld\n", ans);
	}
}

T2:Visit

最开始T2是树上染色,疯狂码,结果换题了,郁闷。。。。

有一个显然的\(O(N^3)\)暴力,不提了

算了一下后发现四个方向的次数知1求3,想到枚举其中一个方向,这是就是一个简单的组合计数。

可以发现图形在四个象限没有区别,n和m取abs.设向上为a,向下为b,向右为c,向左为d。

那么总方案数为\(C_T^a * C_{T - a}^b * C_{T - a - b} ^ c * C_{T - a - b - c} ^ d\),最后一项为1,化简为\(C_T^a * C_{T - a}^b * C_{T - a - b} ^ c\),发现和d没啥关系。

\(a - b = m\).所以\(b = a - m\).又因为\(a + c - b - d = n + m, a + c + b + d = t\),所以\(2(a + c) = t + n + m\)。所以\(c = \frac{t + n + m - 2a}{2}\).

然后读错数据范围,以为60%的数据模数为质数,开始狂写质因数分解求组合数,码码码码码。结果不知道咋回事调不出来,GG。T3也没时间写了。。。随便写了一下暴力,结果着急写错了,本次考试凉凉。

事实上只有30%的数据模数为质数

那么模数不是质数该怎么办呢?可以使用ExLucas.

首先给出了每一个质因子次数为1,这太好了( 我们先把模数质因数分解,用每个质因数作为模数计算一个答案。最后使用CRT合并答案。

答案的式子是可以化成\(\frac{T!}{a!*b!*c!*d!}\)的,但这玩意求不了。。。还是用组合形式,每次预处理出1到\(min(T, prime[i])\)的阶乘,用Lucas求。

记得多取模

可以判一下非法情况,当T和(n + m)奇偶性不同时无解然而数据并没有无解的情况

要不再证一下CRT数学苦手今天好惨

CRT用来求解一组由形如\(x_i \equiv a_i \mod m_i\)组成的线性同余方程组。

我们使用构造的方法得到解:

我们设\(M = \prod \limits_{i = 1}^{n} m_i\),$t_i = \frac{M}{m_i} \(即,M是所有\)m_i\(的最小公倍数,t是除\)m_i$外所有模数的乘积。

现在我们要为每个方程找一个\(y_i\),使得\(y_i \equiv 0 \mod m_k, k != i, y_i \equiv 1 \mod m_i\).第一个式子使得乘\(y_i\)对其他方程没有影响,第二个式子使得\(y_i * a_i \equiv a_i \mod m_i\).

由第一个式子,\(y_i = t_i * QAQ\). 由第二个式子,\(t_i * QAQ \equiv 1 \mod m_i\),即\(QAQ\)\(t_i\)在模\(m_i\)意义下的乘法逆元。

所以,最后$x = \prod \limits_{i = 1}^n a_i t_i t_i ^ {-1} $.

考试时真没想到要CRT,我还是Too young,需要学习一个

#include <bits/stdc++.h>
#define ll long long

const int N = 100233;
ll T, mod, n, m, a, b, c, d, fac[N], pri[N], ans[N];

ll Qpow(ll x, int b, ll p) {
	ll ret = 1;
	for (; b; b >>= 1, x = x * x % p)
		if (b & 1) ret = ret * x % p;
	return ret;
}

ll CRT() {
	ll ret = 0, lcm = 1;
	for (int i = 1; i <= pri[0]; i++) lcm *= pri[i];
	for (int i = 1; i <= pri[0]; i++) {
		ll t = lcm / pri[i];
		ret = (ret + t * ans[i] * Qpow(t, pri[i] - 2, pri[i])) % lcm;
	}
	return (ret + lcm) % lcm;
}

ll C(ll n, ll m, ll p) {
	if (m > n) return 0;
	if (n == 0 || m == n) return 1;
	return fac[n] * Qpow(fac[m], p - 2, p) % p * Qpow(fac[n - m], p - 2, p) % p;
}

ll Lucas(ll n, ll m, ll p) {
	return m == 0 ? 1 : C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p;
}

void init(int id) {
	fac[1] = fac[0] = 1;
	for (int i = 2; i <= std::min(T, pri[id]); i++) fac[i] = fac[i - 1] * i % pri[id];
}

void GG() {
	puts("0");
	exit(0);
}

signed main() {
	scanf("%lld%lld%lld%lld", &T, &mod, &n, &m);
	n = abs(n), m = abs(m);
	if ((T & 1) != ((n + m) & 1) || T < (n + m)) GG();
	ll tmp = mod;
	for (int i = 2; i * i <= mod; i++)
		if (tmp % i == 0) pri[++pri[0]] = i, tmp /= i;
	if (tmp > 1) pri[++pri[0]] = tmp;
	for (int i = 1; i <= pri[0]; i++) {
		init(i);
		for (int j = m; j <= T; j++) {
			a = j, b = a - m, c = (T + m + n - 2 * a) / 2, d = c - n;
			if (d < 0) break;
			ans[i] = (ans[i] + Lucas(T, a, pri[i]) % pri[i] * Lucas(T - a, b, pri[i]) % pri[i] * Lucas(T - a - b, c, pri[i]) % pri[i]) % pri[i];
		}
	}
	printf("%lld\n", CRT());
	return 0;
}

T3:光

考试的时候看都没看,GG。

一看就有一个很显然的60分暴力:

写一个while(1)的死循环,开个vis[][][]记录到过的格子、方向,到了到过的格子且方向相同就跳出循环。

把反射和移动都放到一堆数组里,这样省得判断。

作为附中压行之王,我坚信我是附中最短传说。

#include <bits/stdc++.h>

int n, m, k, xs, ys, dir, ans;
const int dy[] = {0, -1, -1, 1, 1}, dx[] = {0, -1, 1, 1, -1}, r1[] = {0, 2, 1, 4, 3}, r2[] = {0, 4, 3, 2, 1}, r3[] = {0, 3, 4, 1, 2};
bool wall[1005][1005], vis[1005][1005][6];
char ds[5];

signed main() {
	scanf("%d%d%d", &n, &m, &k);
	if (n > 1000 || m > 1000) {printf("%lld\n", (long long) n * m); return 0;}
	for (int i = 1, x, y; i <= k; i++)
		scanf("%d%d", &x, &y), wall[x][y] = 1;
	scanf("%d%d%s", &xs, &ys, ds);
	dir = ds[0] == 'N' ? ds[1] == 'E' ? 2 : 1
		: ds[1] == 'E' ? 3 : 4;
	for (int i = 0; i <= n + 1; i++) wall[i][0] = wall[i][m + 1] = 1;
	for (int i = 0; i <= m + 1; i++) wall[0][i] = wall[n + 1][i] = 1;
	while (1) {
		if (vis[xs][ys][dir]) break;
		vis[xs][ys][dir] = 1;
		int xx = xs + dx[dir], yy = ys + dy[dir];
		if (!wall[xx][yy]) {xs = xx, ys = yy; continue;} //撞不到	
		if (wall[xx][ys] && !wall[xs][yy]) {dir = r1[dir], ys = yy; continue;} //撞墙
		if (!wall[xx][ys] && wall[xs][yy]) {dir = r2[dir], xs = xx; continue;}	
		if (wall[xx][ys] && wall[xs][yy]) {dir = r3[dir]; continue;} //撞到角了
		dir = r3[dir];	
	}
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= m; j++)
			if (vis[i][j][1] || vis[i][j][2] || vis[i][j][3] || vis[i][j][4]) 
				ans++;
	printf("%d\n", ans);
	return 0;
}

正解doc看不懂,向郭老师学习。正解就是个优化过的暴力。。。。。

首先图肯定不能开个二维数组存,不然你必死(

光线肯定走几条斜线,观察图可以发现性质,两种斜线分别x+y相同或x-y相同。于是开一堆vector,往每个vector仍每条斜线上的障碍物的x。

然后大力模拟,走的时候不用一步一步走了,直接upper_bound.这就是正解优化的地方,从一步一步走到走很多步。。。

关于结束的问题,有一个很简单的思路。可以先让光线走一次,从第一个障碍物那里开始模拟,第二次到这个障碍物便是走了一圈了。

特别的,如果180度反弹,直接跳出递归,反方向找。

我觉得我还是蛮短的。作为附中压行之王,我坚信我即使不是附中最短传说,也是附中次短传说。

#include <bits/stdc++.h>

int n, m, k, xs, ys, dir, start_x, start_y, vised;
long long ans; bool ret;
const int N = 100233, dy[] = {0, -1, -1, 1, 1}, dx[] = {0, 1, -1, 1, -1};
int r1[] = {0, 2, 1, 4, 3}, r2[] = {0, 3, 4, 1, 2}, r3[] = {0, 4, 3, 2, 1};
std::vector<int> l1[N<<1], l2[N<<2]; //l1:"/", l2:"\"
char ds[5];

void Rush(int x, int y, int dir) {
	int tmp = 0;
	switch (dir) {
		case 1:
			tmp = std::upper_bound(l1[x+y].begin(), l1[x+y].end(), x) - l1[x+y].begin();
			xs = l1[x+y][tmp], ys = x + y - xs;
			ans += xs - x;
			break;	
		case 2:
			tmp = std::upper_bound(l2[x-y+N].begin(), l2[x-y+N].end(), x) - l2[x-y+N].begin();
			xs = l2[x-y+N][tmp-1], ys = xs - x + y; 
			ans += x - xs;
			break;
		case 3:
			tmp = std::upper_bound(l2[x-y+N].begin(), l2[x-y+N].end(), x) - l2[x-y+N].begin();
			xs = l2[x-y+N][tmp], ys = xs - x + y;
			ans += xs - x;
			break;
		case 4:
			tmp = std::upper_bound(l1[x+y].begin(), l1[x+y].end(), x) - l1[x+y].begin();
			xs = l1[x+y][tmp-1], ys = x + y - xs;
			ans += x - xs;
			break;
	}
}

bool is_banned(int x, int y) {
	int tmp = std::lower_bound(l1[x+y].begin(), l1[x+y].end(), x) - l1[x+y].begin();
	return l1[x+y][tmp] == x;
}

void Move(int x, int y, int d) {
	if (x == start_x && y == start_y && ++vised > 1) return;
	Rush(x, y, d);
	if (is_banned(xs, ys-dy[d]) ^ is_banned(xs-dx[d], ys)) {
		if (is_banned(xs, ys-dy[d])) Move(xs - dx[d], ys, r1[d]);
		else Move(xs, ys - dy[d], r2[d]);
	} else ret = 1;
}

void Add(int x, int y) {
	l1[x+y].push_back(x), l2[x-y+N].push_back(x);
}

signed main() {
	scanf("%d%d%d", &n, &m, &k);
	for (int i = 1, x, y; i <= k; i++)
		scanf("%d%d", &x, &y), l1[x+y].push_back(x), l2[x-y+N].push_back(x);
	scanf("%d%d%s", &start_x, &start_y, ds);
	dir = ds[0] == 'N' ? ds[1] == 'E' ? 1 : 2 : ds[1] == 'E' ? 3 : 4;
    for (int i = 0; i <= n + 1; i++) Add(i, 0), Add(i, m + 1);
	for (int i = 0; i <= m + 1; i++) Add(0, i), Add(n + 1, i);
	for (int i = 0; i <= n + m + 2; i++) std::sort(l1[i].begin(), l1[i].end());
	for (int i = N - m - 1; i <= N + n + 1; i++) std::sort(l2[i].begin(), l2[i].end());
	Rush(start_x, start_y, dir);
	if (is_banned(xs, ys-dy[dir]) ^ is_banned(xs-dx[dir], ys)) {
		if (is_banned(xs, ys-dy[dir])) start_x = xs - dx[dir], start_y = ys, dir = r1[dir];
		else start_x = xs, start_y = ys - dy[dir], dir = r2[dir];
	} else start_x = xs - dx[dir], start_y = ys - dy[dir], dir = r3[dir]; ans = 0;
	Move(start_x, start_y, dir);
	if (ret) vised = 0, Move(start_x, start_y, r3[dir]), --ans;
	printf("%lld\n", ans);
	return 0;
}

总结

这次考试最大一个问题就是忘了数学那一堆公式。。。。血淋淋的教训告诉我们死记是不会有好的江来的(

还是要自己证明定理,不然必忘

还有一个问题,调不出来题就先把暴力打了看看下一个,别吊死在一棵树上,最后还没调出来,树倒了。。。。

posted @ 2019-07-22 19:22  Gekoo  阅读(239)  评论(3编辑  收藏  举报