题意:
给定依次n个实验,每个实验有pi%概率获得ki点学分,修够27分就不再继续实验,问成功修够的话,期望要实验多少次。
n <= 10, ki <= 10, pi <= 100。
题解:
概率dp,f[i][j]表示做到第i个实验获得j学分的概率,最后统计一下所有可以修够学分的事件概率,算出成功实验的期望次数。
代码:
#include <cstdio> #include <cstring> const int maxn = 11, maxv = 27; int n, cnt; double f[maxn][maxv], p1, p2; int main() { while(scanf("%d", &n) != EOF) { cnt = 0; p1 = p2 = 0; memset(f, 0, sizeof f); f[0][0] = 1; for(int i = 1; i <= n; ++i) { int c, p; scanf("%d%d", &c, &p); if(p) cnt += c; for(int j = 0; j < maxv; ++j) { if(j + c >= maxv) { p1 += p / 100.0 * f[i - 1][j] * i; p2 += p / 100.0 * f[i - 1][j]; } else f[i][j + c] += p / 100.0 * f[i - 1][j]; f[i][j] += (100 - p) / 100.0 * f[i - 1][j]; } } if(cnt < maxv) puts("-1"); else printf("%.4f\n", p1 / p2); } return 0; }
题意:
给定一个二维平面的flappy bird游戏,bird起初在(0, 0)处,现在垂直x轴有n个可以从中通过的柱子(可以蹭着边缘通过),但是每移动一单位需要耗费一点体力,问有m点体力的情况下最多通过几个柱子(可以是恰好通过)。注意移动的距离按欧几里得距离算。
n <= 1000, m <= 10^9, 坐标 <= 10^5。
题解:
视野型动态规划,最优路径一定是先走柱子的端点再横着通过某个柱子,这样一定最短,可以用一个橡皮筋模型来思考一下。
所以对于每个柱子,定义f[i][0]表示到达柱子下端点的最短路长度,f[i][1]表示到达柱子上端点的最短路长度,f[i][2]表示横着穿越该柱子停下的最短路长度。
而两个端点(或者直走)能转移的条件就是当前视野里能看到要到达的点,维护上下视野的向量即可,用叉积可以避免浮点精度误差地判断视野,时间复杂度O(n^2)。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxm = 1010; int n; long long m, x[maxm], l[maxm], h[maxm]; double f[maxm][3]; inline long long det(long long x1, long long y1, long long x2, long long y2) { return x1 * y2 - x2 * y1; } inline double dis(long long x, long long y) { return sqrt(x * x + y * y); } int main() { while(scanf("%lld%d", &m, &n) == 2) { x[0] = l[0] = h[0] = 0; for(int i = 1; i <= n; ++i) { scanf("%lld%lld%lld", x + i, l + i, h + i); f[i][0] = f[i][1] = f[i][2] = 2e9; } f[0][0] = f[0][1] = f[0][2] = 0; for(int i = 0, lpos, hpos; i < n; ++i) { lpos = i + 1, hpos = i + 1; for(int j = i + 1; j <= n; ++j) { if(det(x[j] - x[i], l[j] - l[i], x[lpos] - x[i], l[lpos] - l[i]) <= 0) { if(det(x[j] - x[i], l[j] - l[i], x[hpos] - x[i], h[hpos] - l[i]) >= 0) f[j][0] = min(f[j][0], f[i][0] + dis(x[j] - x[i], l[j] - l[i])); lpos = j; } if(det(x[j] - x[i], h[j] - l[i], x[hpos] - x[i], h[hpos] - l[i]) >= 0) { if(det(x[j] - x[i], h[j] - l[i], x[lpos] - x[i], l[lpos] - l[i]) <= 0) f[j][1] = min(f[j][1], f[i][0] + dis(x[j] - x[i], h[j] - l[i])); hpos = j; } if(det(x[lpos] - x[i], l[lpos] - l[i], x[hpos] - x[i], h[hpos] - l[i]) < 0) break; if(l[lpos] <= l[i] && l[i] <= h[hpos]) f[j][2] = min(f[j][2], f[i][0] + x[j] - x[i]); } lpos = i + 1, hpos = i + 1; for(int j = i + 1; j <= n; ++j) { if(det(x[j] - x[i], l[j] - h[i], x[lpos] - x[i], l[lpos] - h[i]) <= 0) { if(det(x[j] - x[i], l[j] - h[i], x[hpos] - x[i], h[hpos] - h[i]) >= 0) f[j][0] = min(f[j][0], f[i][1] + dis(x[j] - x[i], l[j] - h[i])); lpos = j; } if(det(x[j] - x[i], h[j] - h[i], x[hpos] - x[i], h[hpos] - h[i]) >= 0) { if(det(x[j] - x[i], h[j] - h[i], x[lpos] - x[i], l[lpos] - h[i]) <= 0) f[j][1] = min(f[j][1], f[i][1] + dis(x[j] - x[i], h[j] - h[i])); hpos = j; } if(det(x[lpos] - x[i], l[lpos] - h[i], x[hpos] - x[i], h[hpos] - h[i]) < 0) break; if(l[lpos] <= h[i] && h[i] <= h[hpos]) f[j][2] = min(f[j][2], f[i][1] + x[j] - x[i]); } } int ans = 0; for(int i = 0; i <= n; ++i) if(m >= min(f[i][2], min(f[i][0], f[i][1]))) ans = i; printf("%d\n", ans); } return 0; }
题意:
给定一个初始长度为L,贴着三个坐标轴(正方向)的正方体,现在将正方体旋转平移放缩,给出新的八个顶点坐标,再询问原正方体里的某点对应新正方体的某点是多少。
所有数 <= 1000。
题解:
不妨选原正方体的三个基向量(1, 0, 0), (0, 1, 0), (0, 0, 1),看它们在坐标变换后变成了哪三个向量,直接按照新的基向量和原点来找新的点坐标。
代码:
#include <cstdio> int L, a, b, c; double x[8], y[8], z[8], xx, yy, zz; int main() { while(scanf("%d", &L) != EOF) { xx = yy = zz = 0; for(int i = 0; i < 8; ++i) scanf("%lf%lf%lf", x + i, y + i, z + i); scanf("%d%d%d", &a, &b, &c); x[1] -= x[0], x[2] -= x[0], x[4] -= x[0]; y[1] -= y[0], y[2] -= y[0], y[4] -= y[0]; z[1] -= z[0], z[2] -= z[0], z[4] -= z[0]; xx = x[1] * a / L + x[2] * b / L + x[4] * c / L + x[0]; yy = y[1] * a / L + y[2] * b / L + y[4] * c / L + y[0]; zz = z[1] * a / L + z[2] * b / L + z[4] * c / L + z[0]; printf("%.3f %.3f %.3f\n", xx, yy, zz); } return 0; }
题意:
给定一个1~n的排列,支持两种操作,排列循环右移x位,求当前排列的逆序对数。m次操作。
n, m <= 10^5, x <= 10^9。
题解:
对于原排列,可以利用树状数组维护某些权值出现的次数的前缀和,来快速计算每个位和之前位产生的逆序对数,时间复杂度O(nlogn)。
总共只有n种不同的排列,考虑当前排列和循环右移一位的排列之间逆序对数的变化,即原来最后一位产生的逆序对消失,不是逆序对的变成逆序对,可以通过一次对树状数组的查询得到,单次时间复杂度O(logn),预处理出所有可能的排列的逆序对数时间复杂度为O(nlogn)。
然后就是维护当前循环右移了多少位即可。
代码:
#include <cstdio> #include <cstring> const int maxn = 100010; int n, m, a[maxn], bit[maxn], delta; long long f[maxn]; void add(int x) { for( ; x <= n; x += x & -x) ++bit[x]; } int sum(int x) { int ret = 0; for( ; x > 0; x -= x & -x) ret += bit[x]; return ret; } int main() { while(scanf("%d%d", &n, &m) == 2) { f[0] = delta = 0; memset(bit, 0, sizeof bit); for(int i = 0; i < n; ++i) { scanf("%d", a + i); add(a[i]); f[0] += i - sum(a[i] - 1); } for(int i = 1; i < n; ++i) f[i] = f[i - 1] + sum(a[n - i] - 1) * 2 - n + 1; while(m--) { char op[2]; int x; scanf("%s", op); if(op[0] == 'Q') printf("%lld\n", f[delta]); else { scanf("%d", &x); delta = (delta + x) % n; } } } return 0; }
题意:
数轴上有n个点,每次可以选择一个位置x,将这个位置及其后面的k个位置(x, x + d, x + 2 * d, ..., x + (k - 1) * d)上的点删除,问最少要做几次删除操作才能删掉所有的点。
n <= 1000, 坐标, k, d <= 10^9。
题解:
按坐标升序依次枚举每个未删除的点,删除它并看后面能尽量删掉哪些点,时间复杂度O(n^2)。
代码:
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 1001; int n, k, d, x[maxn], ans; int main() { while(scanf("%d%d%d", &n, &k, &d) == 3) { ans = 0; for(int i = 0; i < n; ++i) scanf("%d", x + i); sort(x, x + n); for(int i = 0; i < n; ++i) if(x[i]) { ++ans; for(int j = i + 1; j < n; ++j) if(x[j] && (x[j] - x[i]) % d == 0) if((x[j] - x[i]) / d >= k) break; else x[j] = 0; x[i] = 0; } printf("%d\n", ans); } return 0; }
题意:
数轴上有n个点,坐标xi为自然数,现在要将它们移动到连续的n个整数的位置,每个点移动的代价是两个位置之间的距离,求代价之和的最小值。
n <= 1000, xi <= 10^6。
题解:
如果点按照升序排序,则可以枚举不动的那个点的位置,然后计算其他点移动到它附近的代价,判断即可,时间复杂度O(n^2)。
实际上,终态应该是{xi - i}相等的位置,则可以对xi排序,之后{xi - i}也一定是有序的,从新序列里取中位数作为中心直接计算代价即可,时间复杂度O(nlogn)。
代码:
#include <cstdio> #include <algorithm> using namespace std; const int maxn = 1001; int n, a[maxn], pos, ans; int main() { while(scanf("%d", &n) != EOF) { for(int i = 0; i < n; ++i) scanf("%d", a + i); sort(a, a + n); for(int i = 0; i < n; ++i) a[i] -= i; pos = a[n >> 1]; ans = 0; for(int i = 0; i < n; ++i) ans += a[i] <= pos ? pos - a[i] : a[i] - pos; printf("%d\n", ans); } return 0; }
题意:
给定一个分为n段的函数,问其和左边界、右边界、x轴之上所交的图形是否封闭,如果封闭,求其面积。
n <= 100, |坐标| <= 10^4。
题解:
将在x轴之上的部分的区间计算出来,在计算积分的同时判断是否存在间断点即可,细节题。
代码:
#include <cmath> #include <cstdio> #include <cstring> const int maxn = 101; const double eps = 1e-12; int n, l, r, a[maxn], b[maxn], c[maxn], s[maxn]; inline int dcmp(double x) { if(fabs(x) < eps) return 0; return 0 < x ? 1 : -1; } inline double area(int id, double L, double R) { return ((1.0 / 3 * a[id] * R + 1.0 / 2 * b[id]) * R + c[id]) * R - ((1.0 / 3 * a[id] * L + 1.0 / 2 * b[id]) * L + c[id]) * L; } inline double f(int id, double x) { return (a[id] * x + b[id]) * x + c[id]; } double lastx, lasty, ans; bool Area(int id, double L, double R) { if(!dcmp(lastx - L) && dcmp(lasty - f(id, L))) return 0; ans += area(id, L, R); lastx = R; lasty = f(id, lastx); return 1; } int main() { while(scanf("%d%d%d", &n, &l, &r) == 3) { bool flag = 1; for(int i = 0; i < n; ++i) scanf("%d%d%d%d", a + i, b + i, c + i, s + i); ans = 0.0; lastx = l - 1; for(int i = 0; i < n; ++i) { int L = s[i], R = i == n - 1 ? r : s[i + 1]; if(!a[i]) { if(!b[i]) { if(c[i] > 0) flag &= Area(i, L, R); } else { double x = -(double)c[i] / b[i]; if(dcmp(f(i, L)) >= 0 && dcmp(f(i, R)) >= 0) flag &= Area(i, L, R); else if(dcmp(f(i, L)) > 0) flag &= Area(i, L, x); else if(dcmp(f(i, R)) > 0) flag &= Area(i, x, R); } } else { if(b[i] * b[i] - 4 * a[i] * c[i] <= 0) { if(a[i] > 0) flag &= Area(i, L, R); } else { double x1 = (-b[i] - sqrt(b[i] * b[i] - 4 * a[i] * c[i])) / (2 * a[i]), x2 = (-b[i] + sqrt(b[i] * b[i] - 4 * a[i] * c[i])) / (2 * a[i]); double RR = x1 < R ? x1 : R, LL = L < x2 ? x2 : L; if(a[i] > 0)//x1 < x2 { if(dcmp(L - RR) < 0) flag &= Area(i, L, RR); if(dcmp(LL - R) < 0) flag &= Area(i, LL, R); } else//x1 > x2 { if(dcmp(LL - RR) < 0) flag &= Area(i, LL, RR); } } } if(i) { double f1 = f(i - 1, s[i]), f2 = f(i, s[i]); if(dcmp(f1) >= 0 && dcmp(f2) >= 0 && dcmp(f1 - f2) || dcmp(f1) * dcmp(f2) < 0) flag = 0; } if(!flag) break; } if(!flag) puts("0.000"); else printf("%.3f\n", ans); } return 0; }
题意:
给定一个长度为n的非负整数序列,有m个操作,操作有两种,将一段区间的每个数平方,求一段区间的每个数之和的平方模61的值。
n, m <= 10^5, 序列元素在int范围。
题解:
首先可以想到将所有数模61,在模意义下维护更加轻松。
而61是一个质数,对于小于61的自然数a,有a ^ 60 mod 61 = 1,则有a ^ 64 mod 61 = a ^ 4,或者说a ^ (2 ^ 6) mod 61 = a ^ (2 ^ 2)。
每个数的2次方幂存在循环节,所以可以在线段树上维护每个数x的x, x ^ 2, x ^ 4, x ^ 8, x ^ 16, x ^ 32,区间同理,则区间平方操作即为数组右移操作,区间求和则直接求就可以了,时间复杂度O(6mlogn)。
代码:
#include <cstdio> #include <cstring> const int maxn = 131072 << 1, mod = 61; int n, m; struct SegTree { int sum[6], tag; } seg[maxn]; int sqr(int x) { return x * x % mod; } void push_up(int o) { for(int i = 0, now1 = seg[o + o].tag, now2 = seg[o + o + 1].tag; i < 6; ++i) { seg[o].sum[i] = seg[o + o].sum[now1++] + seg[o + o + 1].sum[now2++]; if(seg[o].sum[i] >= mod) seg[o].sum[i] -= mod; if(now1 >= 6) now1 = 2; if(now2 >= 6) now2 = 2; } } void push_down(int o) { if(!seg[o].tag) return; seg[o + o].tag += seg[o].tag; if(seg[o + o].tag >= 6) seg[o + o].tag = (seg[o + o].tag - 2) % 4 + 2; seg[o + o + 1].tag += seg[o].tag; if(seg[o + o + 1].tag >= 6) seg[o + o + 1].tag = (seg[o + o + 1].tag - 2) % 4 + 2; seg[o].tag = 0; } void build(int o, int L, int R) { if(L == R) { scanf("%d", &seg[o].sum[0]); seg[o].sum[0] %= mod; for(int i = 1; i < 6; ++i) seg[o].sum[i] = sqr(seg[o].sum[i - 1]); return; } int M = L + R >> 1; build(o + o, L, M); build(o + o + 1, M + 1, R); push_up(o); } void mul(int o, int L, int R, int l, int r) { if(L == l && R == r) { ++seg[o].tag; if(seg[o].tag >= 6) seg[o].tag = 2; return; } int M = L + R >> 1; push_down(o); if(r <= M) mul(o + o, L, M, l, r); else if(l > M) mul(o + o + 1, M + 1, R, l, r); else { mul(o + o, L, M, l, M); mul(o + o + 1, M + 1, R, M + 1, r); } push_up(o); } int query(int o, int L, int R, int l, int r) { if(L == l && R == r) return seg[o].sum[seg[o].tag]; int M = L + R >> 1, ret = 0; push_down(o); if(r <= M) ret = query(o + o, L, M, l, r); else if(l > M) ret = query(o + o + 1, M + 1, R, l, r); else { ret = query(o + o, L, M, l, M) + query(o + o + 1, M + 1, R, M + 1, r); if(ret >= mod) ret -= mod; } push_up(o); return ret; } int main() { while(scanf("%d%d", &n, &m) == 2) { memset(seg, 0, sizeof seg); build(1, 1, n); while(m--) { int l, r; char op[2]; scanf("%s%d%d", op, &l, &r); if(op[0] == 'S') mul(1, 1, n, l, r); else printf("%d\n", sqr(query(1, 1, n, l, r))); } } return 0; }
题意:
给定麻将一个初始牌面,再给定接下来牌池的依次会出现的牌面,保证可以在没牌拿之前胡牌,胡牌形式有三种(普通胡、七对子、国士无双),对于每组数据输出合法的方案。
题解:
如果不考虑比较坑爹的评测机,那么这是一道很简单的模拟题,对所有的牌进行一次判定胡牌的操作,看要选哪些牌,输出即可。
但是这个题有Special Judge,是根据选手的输出序列模拟麻将操作,在现在的OJ上需要尽量让输出序列短才能在比较器不超时的情况下AC,所以还需要二分牌池里要摸的牌数,找出最早胡牌的策略。
代码:
#include <cstdio> #include <cstring> const char *out = "mspc"; int n, now[20], seq[150], all[40], wan[40], fin, push[150]; char str[150]; int trans(char *s) { for(int i = 0; i < 4; ++i) if(s[1] == out[i]) return i * 9 + s[0] - '1'; return -1; } bool matchless() { bool flag = 0; for(int i = 0; i < 3; ++i) { if(!all[i * 9] || !all[i * 9 + 8]) return 0; --all[i * 9], --all[i * 9 + 8]; ++wan[i * 9], ++wan[i * 9 + 8]; if(!flag && all[i * 9]) { --all[i * 9]; ++wan[i * 9]; flag = 1; } if(!flag && all[i * 9 + 8]) { --all[i * 9 + 8]; ++wan[i * 9 + 8]; flag = 1; } } for(int i = 0; i < 7; ++i) { if(!all[27 + i]) return 0; --all[27 + i]; ++wan[27 + i]; if(!flag && all[27 + i]) { --all[27 + i]; ++wan[27 + i]; flag = 1; } } if(flag) return 1; return 0; } bool sevenpair() { int cnt = 0; for(int i = 0; i < 34 && cnt < 7; ++i) if(all[i] >= 2) { all[i] -= 2; wan[i] += 2; ++cnt; } if(cnt == 7) return 1; } bool check(int dep) { if(dep == 4) { for(int i = 0; i < 34; ++i) if(all[i] >= 2) { all[i] -= 2; wan[i] += 2; return 1; } return 0; } for(int i = 0; i < 34; ++i) if(all[i] >= 3) { all[i] -= 3; wan[i] += 3; if(check(dep + 1)) return 1; all[i] += 3; wan[i] -= 3; } for(int t = 0; t < 3; ++t) for(int j = 0; j < 7; ++j) { int i = t * 9 + j; if(all[i] && all[i + 1] && all[i + 2]) { --all[i], --all[i + 1], --all[i + 2]; ++wan[i], ++wan[i + 1], ++wan[i + 2]; if(check(dep + 1)) return 1; ++all[i], ++all[i + 1], ++all[i + 2]; --wan[i], --wan[i + 1], --wan[i + 2]; } } return 0; } void rebuild() { for(int i = 0; i < 34; ++i) { all[i] += wan[i]; wan[i] = 0; } } bool judge(int lim) { memset(all, 0, sizeof all); memset(wan, 0, sizeof wan); for(int i = 0; i < 13; ++i) ++all[now[i]]; for(int i = 0; i < lim; ++i) ++all[seq[i]]; if(!matchless()) { rebuild(); if(!sevenpair()) { rebuild(); if(!check(0)) return 0; } } return 1; } int main() { while(scanf("%s", str) != EOF) { now[0] = trans(str); ++all[now[0]]; for(int i = 1; i < 13; ++i) { scanf("%s", str); now[i] = trans(str); ++all[now[i]]; } scanf("%d", &n); for(int i = 0; i < n; ++i) { scanf("%s", str); seq[i] = trans(str); ++all[seq[i]]; } int L = 0, R = n, M; while(L < R) { M = L + R >> 1; if(judge(M)) R = M; else L = M + 1; } judge(L); fin = push[0] = 0; for(int i = 0; i < 13; ++i) if(wan[now[i]]) { --wan[now[i]]; ++fin; } else push[++push[0]] = now[i]; for(int i = 0; i < n; ++i) { if(wan[seq[i]]) { --wan[seq[i]]; ++fin; } else push[++push[0]] = seq[i]; if(fin == 14) { puts("Ron"); break; } printf("%d%c\n", push[push[0]] % 9 + 1, out[push[push[0]] / 9]); --push[0]; } } return 0; }
我很好奇我是怎么在精度大战中存活的,感觉自己弱爆了。现场赛的开题顺序不对,当时的做题心态也不是很好,不过终于补全了题目,尤其是最后一题,如果我NOI没写那个麻将AI估计这题是连题目都不会想看的吧。