NOIP模拟测试7
考爆了,爆零,GG.
T1:方程的解
一眼Exgcd,求出最小解,再求出所有解。
然而Exgcd忘了,面向数据范围编程,特判少了,40分滚粗。
这题要特判的太多了。。。
- 要注意特判0
- 要注意特判符号
- 要注意特判有无正整数解
大体来说就是这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;
}
总结
这次考试最大一个问题就是忘了数学那一堆公式。。。。血淋淋的教训告诉我们死记是不会有好的江来的(
还是要自己证明定理,不然必忘
还有一个问题,调不出来题就先把暴力打了看看下一个,别吊死在一棵树上,最后还没调出来,树倒了。。。。