• 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} floor),所以(bx + (a - b * lfloor frac{a}{b} floor)y = g).即:(ay + b(x - lfloor frac{a}{b} floor y) = g).

    可得原方程一组解 (x_1 = y, y_1 = x - lfloor frac{a}{b} floor 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
    ", 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
    ", 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
    ", (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
    ", 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
    ", ans);
    	return 0;
    }
    

    总结

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

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

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

  • 相关阅读:
    Eclipse 添加行号
    http中 get方法 传送中文参数乱码解决办法
    第一章 java 语言概述
    Python学习
    Python学习
    Python学习
    Python学习
    Python学习
    Python学习
    Python学习
  • 原文地址:https://www.cnblogs.com/gekoo/p/11227861.html
Copyright © 2020-2023  润新知