北航校赛2014 决赛 题解

来源:互联网 发布:淘宝店在哪进货 编辑:程序博客网 时间:2024/05/17 03:08

比赛地址

第十届北航程序设计竞赛现场决赛


A. jsy与他的物理实验

题意:

给定依次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];}elsef[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");elseprintf("%.4f\n", p1 / p2);}return 0;}

B. flappy bird

题意:

给定一个二维平面的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;}

C. Kinfu的奥秘

题意:

给定一个初始长度为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;}

D. 这样还真是令人高兴啊

题意:

给定一个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;}

E. 已经没有什么好怕的了

题意:

数轴上有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;elsex[j] = 0;x[i] = 0;}printf("%d\n", ans);}return 0;}

F. 奇迹和魔法都是存在的

题意:

数轴上有n个点,坐标xi为自然数,现在要将它们移动到连续的n个整数的位置,每个点移动的代价是两个位置之间的距离,求代价之和的最小值。

n <= 1000, xi <= 10^6。

题解:

如果点按照升序排序,则可以枚举不动的那个点的位置,然后计算其他点移动到它附近的代价,判断即可,时间复杂度O(n^2)。

实际上,终态应该是{xi - i}相等的位置,则可以对xi排序,从新序列里取中位数作为中心直接计算代价即可,时间复杂度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;sort(a, a + n);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;}

G. 这种事情我绝对不允许

题意:

给定一个分为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");elseprintf("%.3f\n", ans);}return 0;}

H. 再也不会依赖任何人了

题意:

给定一个长度为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);elseprintf("%d\n", sqr(query(1, 1, n, l, r)));}}return 0;}

I. Microsoft每周日麻训练

题意:

给定麻将一个初始牌面,再给定接下来牌池的依次会出现的牌面,保证可以在没牌拿之前胡牌,胡牌形式有三种(普通胡、七对子、国士无双),对于每组数据输出合法的方案。

题解:

如果不考虑比较坑爹的评测机,那么这是一道很简单的模拟题,对所有的牌进行一次判定胡牌的操作,看要选哪些牌,输出即可。

但是这个题有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;elseL = M + 1;}judge(L);fin = push[0] = 0;for(int i = 0; i < 13; ++i)if(wan[now[i]]){--wan[now[i]];++fin;}elsepush[++push[0]] = now[i];for(int i = 0; i < n; ++i){if(wan[seq[i]]){--wan[seq[i]];++fin;}elsepush[++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估计这题是连题目都不会想看的吧。

0 0