[关闭]
@wwwqeqeqeqe 2019-05-06T02:01:46.000000Z 字数 11719 阅读 765

重庆邮电大学第十届程序设计大赛决赛题解报告



A. 锦神与Jin语言

[Easy] [签到题]

按题意模拟即可

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. void solve() {
  4. int n, fail = 0;
  5. scanf("%d", &n);
  6. for(int i = 0; i < n; ++i) {
  7. int s, t;
  8. scanf("%d%d", &s, &t);
  9. if(t == 0) {
  10. if(s < 90) ++fail;
  11. }else {
  12. if(s != 100) ++fail;
  13. }
  14. }
  15. if(fail == 0) puts("oqm");
  16. else printf("%d\n", fail);
  17. }
  18. int main() {
  19. solve();
  20. return 0;
  21. }

H. 锦神与Jigo语言

[Easy] [字符串处理]

按题意模拟即可,有一些需要注意的细节。取模的一种方法是使用unsigned类型的溢出,所有运算相当于自动对取模;另外也可以用unsigned long long手动取模。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. void solve() {
  4. unsigned ans = 1, num = 0;
  5. int len = 0;
  6. string str;
  7. cin >> str;
  8. str += ' ';
  9. for(char ch : str) {
  10. if(isdigit(ch)) {
  11. num = num*10u + ch - '0';
  12. ++len;
  13. }else {
  14. if(len > 0) {
  15. ans *= num;
  16. }
  17. num = len = 0;
  18. }
  19. }
  20. printf("%u\n", ans);
  21. }
  22. int main() {
  23. solve();
  24. return 0;
  25. }

G. 锦神与Jithon语言

[Easy] [排序]

题意:
是否可以通过交换两相邻元素或者取共轭复数,将A变成B


做法:
交换2相邻元素可以实现任意排列,用抽象代数的语言描述即:
相邻对换可以生成n阶置换群,亦即:


问题转化为是否存在A到B的完美匹配。
实部相等,虚部相等或互为相反数的两个复数可以匹配。
这不是二分图匹配模板题吗?
只需要将A和B的虚部取绝对值,排序,然后判断A和B是否相等。想一想为什么?

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1000 + 5;
  4. int n;
  5. void solve() {
  6. scanf("%d", &n);
  7. vector<pair<int,int> > a(n), b(n);
  8. for(int i = 0; i < n; ++i) {
  9. scanf("%d%d", &a[i].first, &a[i].second);
  10. a[i].second = abs(a[i].second);
  11. }
  12. for(int i = 0; i < n; ++i) {
  13. scanf("%d%d", &b[i].first, &b[i].second);
  14. b[i].second = abs(b[i].second);
  15. }
  16. sort(a.begin(), a.end());
  17. sort(b.begin(), b.end());
  18. puts(a == b ? "yes" : "no");
  19. }
  20. int main() {
  21. solve();
  22. return 0;
  23. }

F. 锦神与JHJ语言

[Medium] [置换分解] [快速幂]

做法1:
置换可以分解为轮换,长度为的轮换内部的周期是,每个单独处理即可。
复杂度

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 5;
  4. int n, m;
  5. int a[N], r[N], tmp[N], vis[N];
  6. int main() {
  7. scanf("%d%d", &n, &m);
  8. for(int i = 0; i < n; ++i) {
  9. scanf("%d", &a[i]);
  10. --a[i];
  11. r[i] = i;
  12. }
  13. for(int i = 0; i < n; ++i) {
  14. if(vis[i]) continue;
  15. vector<int> p;
  16. int x = i;
  17. while(!vis[x]) {
  18. p.push_back(x);
  19. vis[x] = 1;
  20. x = a[x];
  21. }
  22. int len = p.size();
  23. int v = m%len;
  24. if(v > 0) {
  25. for(int j = 0; j < len ; ++j) {
  26. r[p[j]] = p[(j+v)%len];
  27. }
  28. }
  29. }
  30. for(int i = 0; i < n; ++i) {
  31. printf("%d%c", r[i]+1, " \n"[i+1==n]);
  32. }
  33. return 0;
  34. }

做法2:
注意到置换的复合(乘法)满足结合律,因此可以写成幂,自然就可以使用快速幂算法。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 5;
  4. int n, m;
  5. int a[N], r[N], tmp[N];
  6. void mul(int *a, int *b) {
  7. for(int i = 0; i < n; ++i) {
  8. tmp[i] = b[a[i]];
  9. }
  10. memcpy(a, tmp, n*sizeof(int));
  11. }
  12. void solve() {
  13. scanf("%d%d", &n, &m);
  14. for(int i = 0; i < n; ++i) {
  15. scanf("%d", &a[i]);
  16. --a[i];
  17. r[i] = i;
  18. }
  19. for(; m > 0; m >>= 1) {
  20. if(m&1) {
  21. mul(r, a);
  22. }
  23. mul(a, a);
  24. }
  25. for(int i = 0; i < n; ++i) {
  26. printf("%d%c", r[i]+1, " \n"[i+1==n]);
  27. }
  28. }
  29. int main() {
  30. solve();
  31. return 0;
  32. }

C. 锦神与Jin#语言

[Medium] [计算几何]

与三角形是否相交可以转化为与每一条边是否相交。
只要有一个有效交点即与三角形相交。
有效交点不能位于射线端点和三角形端点。
这里推荐使用直线的参数方程求交点,其参数可以方便判断交点是否有效。
详见参考代码。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1000 + 5;
  4. const double eps = 1e-8;
  5. struct point {
  6. double x, y;
  7. void read() { scanf("%lf%lf", &x, &y); }
  8. point operator +(point rhs) {
  9. return (point){x+rhs.x, y+rhs.y};
  10. }
  11. point operator -(point rhs) {
  12. return (point){x-rhs.x, y-rhs.y};
  13. }
  14. point operator *(double rhs) {
  15. return (point){x*rhs, y*rhs};
  16. }
  17. }A, B, C, p, v;
  18. struct line {
  19. point p, v;
  20. double ang;
  21. line() { }
  22. line(point a, point b) {
  23. p = a;
  24. v = b - a;
  25. ang = atan2(v.y, v.x);
  26. }
  27. bool operator < (const line &rhs) const {
  28. return ang < rhs.ang;
  29. }
  30. };
  31. double dot(point A, point B) {
  32. return A.x*B.x + A.y*B.y;
  33. }
  34. double det(point A, point B) {
  35. return A.x*B.y - A.y*B.x;
  36. }
  37. int sgn(double x) {
  38. if(x > eps) return 1;
  39. if(x < -eps) return -1;
  40. return 0;
  41. }
  42. int line_line_intersection(line a, line b) {
  43. point u = a.p - b.p;
  44. double down = det(a.v, b.v);
  45. if(sgn(down) == 0) return 0;
  46. double t1 = det(b.v, u)/down;
  47. double t2 = det(a.v, u)/down;
  48. /** t
  49. segment 0~1
  50. ray 0~inf
  51. line -inf~inf
  52. **/
  53. if(t1 < eps || t1 > 1 - eps || t2 < eps) return 0;
  54. else return 1;
  55. }
  56. int point_in_triangle(point p, point A, point B, point C) {
  57. int t1 = sgn(det(A-p, B-p)), t2 = sgn(det(B-p, C-p)), t3 = sgn(det(C-p, A-p));
  58. return t1*t2 >= 0 && t2*t3 >= 0;
  59. }
  60. void solve() {
  61. A.read();
  62. B.read();
  63. C.read();
  64. p.read();
  65. v.read();
  66. line L(p, p+v);
  67. int ans = line_line_intersection(line(A, B), L) |
  68. line_line_intersection(line(B, C), L) |
  69. line_line_intersection(line(A, C), L);
  70. puts(ans ? "yes" : "no");
  71. }
  72. int main() {
  73. solve();
  74. return 0;
  75. }

D. 锦神与Jin Script语言

[Medium] [状压DP] [期望DP] [容斥原理]

做法1:
一眼状压DP,牢记 概率顺推,期望逆推
用二进制掩码表示当前状态,表示是否得到这种限定
表示当前状态 到达目标状态 需要的期望次数
当前状态可以由 中为 的地方变为 进行转移,即

化简后为:

初始态
最终即是答案

复杂度

  1. #include <cstdio>
  2. #include <map>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 16 + 5;
  6. struct point {
  7. int x, y;
  8. void read() {
  9. scanf("%d%d", &x, &y);
  10. }
  11. point operator -(const point &rhs) const {
  12. return (point){x - rhs.x, y - rhs.y};
  13. }
  14. pair<int,int> get_k() {
  15. int g = __gcd(x, y);
  16. return make_pair(x/g, y/g);
  17. }
  18. }p[N];
  19. pair<int,int> kp[N][N];
  20. int n, a[N], vis[N];
  21. int l[N], r[N];
  22. int ans;
  23. inline void remove(int u) {
  24. r[l[u]] = r[u];
  25. l[r[u]] = l[u];
  26. }
  27. inline void restore(int u) {
  28. r[l[u]] = u;
  29. l[r[u]] = u;
  30. }
  31. void dfs(int k) {
  32. if(k == n) {
  33. map<pair<int,int>, int> cnt;
  34. for(int i = 0; i < n; i += 2) {
  35. ++cnt[kp[a[i]][a[i+1]]];
  36. }
  37. int sum = 0;
  38. for(auto it : cnt) {
  39. sum += it.second*(it.second - 1)/2;
  40. }
  41. ans = max(ans, sum);
  42. return;
  43. }
  44. if(~k&1) {
  45. int x = r[0];
  46. a[k] = x;
  47. remove(x);
  48. for(int y = r[0]; y <= n; y = r[y]) {
  49. a[k+1] = y;
  50. remove(y);
  51. dfs(k+2);
  52. restore(y);
  53. }
  54. restore(x);
  55. }
  56. }
  57. void solve() {
  58. scanf("%d", &n);
  59. for(int i = 1; i <= n; ++i) {
  60. p[i].read();
  61. for(int j = 1; j < i; ++j) {
  62. kp[i][j] = kp[j][i] = (p[i] - p[j]).get_k();
  63. }
  64. }
  65. ans = 0;
  66. r[0] = 1;
  67. for(int i = 1; i <= n; ++i) {
  68. l[i] = i - 1;
  69. r[i] = i + 1;
  70. }
  71. dfs(0);
  72. printf("%d\n", ans);
  73. }
  74. int main() {
  75. solve();
  76. return 0;
  77. }

做法2:
利用容斥原理,容易得到,抽卡次数超过 次才集齐所有限定的概率为



那么,抽卡次数为 时恰好集齐所有限定的概率为



于是,期望为


容易发现,每一项内部的求和都满足形式



求解该式是很经典的级数问题(高中生都会错位相减),大学生的做法通常是





所以,令 即得到


所以


时间复杂度

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 10 + 5;
  4. int n;
  5. double p[N];
  6. double formula() {
  7. double ans = 0;
  8. for(int i = 1; i < 1<<n; ++i) {
  9. int flag = -1;
  10. double sum = 0.0;
  11. for(int j = 0; j < n; ++j) {
  12. if(i&(1<<j)) {
  13. flag = -flag;
  14. sum += p[j];
  15. }
  16. }
  17. ans += flag/sum;
  18. }
  19. return ans;
  20. }
  21. void solve() {
  22. scanf("%d", &n);
  23. for(int i = 0; i < n; ++i) {
  24. scanf("%lf", &p[i]);
  25. }
  26. printf("%.12f\n", formula());
  27. }
  28. int main() {
  29. solve();
  30. return 0;
  31. }

B. 锦神与Jin++语言

[Medium] [搜索]

直接爆搜就能过的水题
随便分析一下:
在地图角落只有种走法,边上有种走法,中间有种。
的规模下,使得中间的格子数最多的是
此时有个角,个边,个中间,走法数大约为
这不是T了吗?
事实上,因为走过的要删掉,搜到后面,很多方向都无法走,方案数会减少不少。
大力出奇迹。

复杂度

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 32;
  4. int n, m;
  5. const int dx[] = {-1, 0, 1, 0};
  6. const int dy[] = {0, 1, 0, -1};
  7. char s[N][N];
  8. int vis[N][N];
  9. int cnt;
  10. int sx, sy, tx, ty;
  11. inline int in(int x, int y) {
  12. return x >= 0 && x < n && y >= 0 && y < m;
  13. }
  14. int get_next(int x, int y, int d, int &nx, int &ny) {
  15. nx = x;
  16. ny = y;
  17. while(true) {
  18. nx += dx[d];
  19. ny += dy[d];
  20. if(!in(nx, ny)) {
  21. return 0;
  22. }
  23. if(!vis[nx][ny] && s[nx][ny] != '#') {
  24. return 1;
  25. }
  26. }
  27. }
  28. vector<int> path;
  29. int dfs(int x, int y, int dir) {
  30. if(x == tx && y == ty) {
  31. if(path.size() == cnt - 1) {
  32. return 1;
  33. }
  34. return 0;
  35. }
  36. vis[x][y] = 1;
  37. for(int d = 0; d < 4; ++d) {
  38. if((d + 2)%4 == dir) continue;
  39. int nx, ny;
  40. if(get_next(x, y, d, nx, ny)) {
  41. path.push_back(d);
  42. if(dfs(nx, ny, d)) {
  43. return 1;
  44. }
  45. path.pop_back();
  46. }
  47. }
  48. vis[x][y] = 0;
  49. return 0;
  50. }
  51. void solve() {
  52. scanf("%d%d", &n, &m);
  53. cnt = 0;
  54. for(int i = 0; i < n; ++i) {
  55. scanf("%s", s[i]);
  56. for(int j = 0; j < m; ++j) {
  57. if(s[i][j] != '#') {
  58. ++cnt;
  59. }
  60. if(s[i][j] == 'S') {
  61. sx = i;
  62. sy = j;
  63. }else if(s[i][j] == 'T') {
  64. tx = i;
  65. ty = j;
  66. }
  67. }
  68. }
  69. path.clear();
  70. memset(vis, 0, sizeof(vis));
  71. if(dfs(sx, sy, 4)) {
  72. static const string ch = "URDL";
  73. puts("yes");
  74. for(int u : path) {
  75. printf("%c", ch[u]);
  76. }
  77. puts("");
  78. }else {
  79. puts("no");
  80. }
  81. }
  82. int main() {
  83. solve();
  84. return 0;
  85. }

E. 锦神与objective-Jin语言

[Hard] [线段树] [字符串Hash]

题意:
的排列 里是否存在三元组满足。即是否存在长度为3的子序列是等差数列。


做法:
考虑固定一个,在左边找 ,右边找
如果将某个数字是否出现在左边存为一个字符串,表示出现过,那么判断是否存在能与组成等差数列的,就可以转化为,判断以为中心的字符串是否是回文串。想一想为什么?
这是因为,如果一对数值上满足条件的同时在的一侧,那么要么同为,要么同为。反之,如果一个是一个是,则A_iA_k$必然一边一个,可以找到这样的三元组。

现在要实现修改字符和判断一个子串是不是回文串。
这是线段树(或树状数组)维护hash的模板题。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef unsigned long long ULL;
  4. const int N = 100000 + 5;
  5. const ULL base = 131;
  6. int T;
  7. int n, a[N];
  8. ULL f[N];
  9. void init() {
  10. f[0] = 1;
  11. for(int i = 1; i < N; ++i) {
  12. f[i] = f[i-1]*base;
  13. }
  14. }
  15. #define root 1,n,1
  16. #define lson l,m,o<<1
  17. #define rson m+1,r,o<<1|1
  18. ULL h1[N*4], h2[N*4];
  19. inline void push_up(int o, int L) {
  20. h1[o] = h1[o<<1]*f[(L>>1)] + h1[o<<1|1];
  21. h2[o] = h2[o<<1|1]*f[L-(L>>1)] + h2[o<<1];
  22. }
  23. void build(int l, int r, int o) {
  24. if(l == r) {
  25. h1[o] = h2[o] = 0;
  26. return;
  27. }
  28. int m = l + r >> 1;
  29. build(lson);
  30. build(rson);
  31. push_up(o, r-l+1);
  32. }
  33. void modify(int p, int l, int r, int o) {
  34. if(l == r) {
  35. h1[o] = h2[o] = 1;
  36. return;
  37. }
  38. int m = l + r >> 1;
  39. if(p <= m) modify(p, lson);
  40. else modify(p, rson);
  41. push_up(o, r-l+1);
  42. }
  43. ULL query1(int L, int R, int l, int r, int o) {
  44. if(L <= l && R >= r) {
  45. return h1[o];
  46. }
  47. int m = l + r >> 1;
  48. if(L > m) return query1(L, R, rson);
  49. else if(R <= m) return query1(L, R, lson);
  50. else return query1(L, m, lson)*f[R-m] + query1(m+1, R, rson);
  51. }
  52. ULL query2(int L, int R, int l, int r, int o) {
  53. if(L <= l && R >= r) {
  54. return h2[o];
  55. }
  56. int m = l + r >> 1;
  57. if(L > m) return query2(L, R, rson);
  58. else if(R <= m) return query2(L, R, lson);
  59. else return query2(L, m, lson) + query2(m+1, R, rson)*f[m-L+1];
  60. }
  61. void solve() {
  62. scanf("%d", &n);
  63. for(int i = 1; i <= n; ++i) {
  64. scanf("%d", &a[i]);
  65. }
  66. build(root);
  67. int flag = 0;
  68. for(int i = 1; i <= n; ++i) {
  69. int x = a[i];
  70. int len = min(x-1, n-x);
  71. int l = x - len, r = x + len;
  72. if(l < r && query1(l, r, root) != query2(l, r, root)) {
  73. flag = 1;
  74. break;
  75. }
  76. modify(x, root);
  77. }
  78. puts(flag ? "yes" : "no");
  79. }
  80. int main() {
  81. init();
  82. solve();
  83. return 0;
  84. }

I. 锦神与Kotjin语言

[Hard] [2-SAT]

每个发射器只有2种选择,是典型的2-SAT模型
对每个格子,往4个方向寻找最近的发射器'S'(遇墙则停)
如果该格子是'S',则4个方向上的发射器不能照到它
如果该格子是'.',按4个方向上的发射器数量讨论:
0: gg
1: 钦定照
2: 两者选1
3: 同一直线上的两个不能照,单独的那个钦定照
4: gg
显然所有需要满足的条件只有2类:

复杂度

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 50 + 5;
  4. int T;
  5. int n, m;
  6. char s[N][N];
  7. int dx[] = {-1, 0, 1, 0};
  8. int dy[] = {0, 1, 0, -1};
  9. int id[N][N], C;
  10. inline int in(int x, int y) {
  11. return x >= 0 && x < n && y >= 0 && y < m;
  12. }
  13. struct TwoSat {
  14. static const int M = N*N*2;
  15. vector<int> G[M];
  16. int mark[M], S[M], C, n;
  17. void init(int _n) {
  18. n = _n;
  19. for(int i = 0; i < n+n; i++) G[i].clear();
  20. memset(mark, 0, sizeof(mark));
  21. C = 0;
  22. }
  23. void clear() {
  24. while(C) mark[S[--C]] = 0;
  25. }
  26. void add_or(int x, int xval, int y, int yval) { /** x or y ==> !x -> y and !y -> x **/
  27. x = x<<1|xval;
  28. y = y<<1|yval;
  29. G[x^1].push_back(y);
  30. G[y^1].push_back(x);
  31. }
  32. void assign(int x, int xval) { /** x = val ==> !x -> x **/
  33. x = x<<1|xval;
  34. G[x^1].push_back(x);
  35. }
  36. int dfs(int u) {
  37. if(mark[u^1]) return 0;
  38. if(mark[u]) return 1;
  39. mark[u] = 1;
  40. S[C++] = u;
  41. for(int v : G[u]) {
  42. if(!dfs(v)) return 0;
  43. }
  44. return 1;
  45. }
  46. int solve() {
  47. for(int i = 0; i < n+n; i += 2) {
  48. if(mark[i] || mark[i+1]) continue;
  49. C = 0;
  50. if(!dfs(i)) {
  51. clear();
  52. if(!dfs(i+1)) return 0;
  53. }
  54. }
  55. return 1;
  56. }
  57. }solver;
  58. int detect(int x, int y, int d) {
  59. while(1) {
  60. x += dx[d];
  61. y += dy[d];
  62. if(!in(x, y) || s[x][y] == '#') return -1;
  63. if(s[x][y] == 'S') return id[x][y];
  64. }
  65. return -1;
  66. }
  67. void solve() {
  68. C = 0;
  69. scanf("%d%d", &n, &m);
  70. for(int i = 0; i < n; ++i) {
  71. scanf("%s", s[i]);
  72. for(int j = 0; j < m; ++j) {
  73. if(s[i][j] == 'S') {
  74. id[i][j] = C++;
  75. }else {
  76. id[i][j] = -1;
  77. }
  78. }
  79. }
  80. solver.init(C);
  81. int ok = 1;
  82. for(int i = 0; i < n; ++i) {
  83. for(int j = 0; j < m; ++j) {
  84. if(s[i][j] == '.') {
  85. int a[4], cnt = 0;
  86. for(int k = 0; k < 4; ++k) {
  87. a[k] = detect(i, j, k);
  88. if(a[k] != -1) {
  89. ++cnt;
  90. }
  91. }
  92. if(cnt == 0 || cnt == 4) {
  93. ok = 0;
  94. break;
  95. }
  96. if(cnt == 1) {
  97. for(int k = 0; k < 4; ++k) {
  98. if(a[k] != -1) {
  99. solver.assign(a[k], k&1);
  100. }
  101. }
  102. }
  103. if(cnt == 2) {
  104. int p = 0, v[2];
  105. for(int k = 0; k < 4; ++k) {
  106. if(a[k] != -1) {
  107. v[p++] = k;
  108. }
  109. }
  110. if(v[0] == (v[1]^2)) {
  111. ok = 0;
  112. break;
  113. }else {
  114. solver.add_or(a[v[0]], v[0]&1, a[v[1]], v[1]&1);
  115. }
  116. }
  117. if(cnt == 3) {
  118. for(int k = 0; k < 4; ++k) {
  119. if(a[k] == -1) {
  120. solver.assign(a[k^2], k&1);
  121. solver.assign(a[k^1], k&1);
  122. solver.assign(a[k^3], k&1);
  123. }
  124. }
  125. }
  126. }else if(s[i][j] == 'S') {
  127. int x = id[i][j];
  128. for(int k = 0; k < 4; ++k) {
  129. int y = detect(i, j, k);
  130. if(y != -1) {
  131. solver.assign(x, ~k&1);
  132. }
  133. }
  134. }
  135. }
  136. }
  137. if(ok && solver.solve()) {
  138. puts("ojs");
  139. for(int i = 0; i < n; ++i) {
  140. for(int j = 0; j < m; ++j) {
  141. if(s[i][j] == 'S') {
  142. int u = id[i][j];
  143. s[i][j] = solver.mark[u<<1] == 1 ? '|' : '-';
  144. }
  145. }
  146. puts(s[i]);
  147. }
  148. }else {
  149. puts("tnl");
  150. }
  151. }
  152. int main() {
  153. solve();
  154. return 0;
  155. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注