-------------------感觉这场小白赛给我难度正好,可能我就是个小白吧。今天总结一下。
题目链接:https://ac.nowcoder.com/acm/contest/549#question
官方题解:https://ac.nowcoder.com/discuss/177449
A .小A的签到题
sol:复制给出的代码当然过不了。这是一个斐波那契数列,由于数据范围比较大,一开始想到矩阵快速幂。但是因为缺少MOD,然后转向找规律。用给出的代码输前几项就很好发现n为奇数结果为1,n为偶数结果为-1。
ps:如果没找到规律用矩阵快速幂或许未尝不可。貌似自然溢出之后也能得到正确结果(未做验证,只是猜想)
- 规律
#include
using namespace std; int main() { long long n; cin >> n; if (n & 1) { puts("1"); } else { puts("-1"); } return 0; }
B .小A的回文串
sol:将字符串在字符串末拷贝一份,然后新字符串的最大回文子串长度和原字符串的长度中比较小的就是结果。
ps:求最大回文子串方法很多。本题数据小,马拉车,后缀数组,动态规划都行。但是马拉车还不会,后缀数组不太熟练,所以我还是用动态规划写了,虽然这是三者中复杂度最高的
- 动态规划
#include "bits/stdc++.h" using namespace std; const int MAXN = 10005; char s[MAXN]; // dp[i][j]表示从i到j的字符串是否构成回文 bool dp[MAXN][MAXN]; int res; int main() { scanf("%s", s + 1); int n = strlen(s + 1); for (int i = 1; i <= n; i++) { s[i + n] = s[i]; } res = 1; for (int i = 1; i <= n * 2; i++) { dp[i][i] = true; if (s[i] == s[i + 1]) { dp[i][i + 1] = true; res = 2; } } for (int i = 3; i <= n; i++) { for (int r = i; r <= n * 2; r++) { int l = r - i + 1; if (s[l] == s[r] && dp[l + 1][r - 1]) { res = i; dp[l][r] = true; } } } cout << res << endl; return 0; }
C .小A买彩票
sol:这个应该是概率dp吧;
- 动态规划
#include "bits/stdc++.h" using namespace std; typedef long long LL; // dp[i][j]表示买i张彩票中j元的方案数 LL dp[35][125]; void init() { for (int i = 1; i <= 4; i++) { dp[1][i] = 1; } for (int i = 2; i <= 30; i++) { for (int j = 1; j <= i * 4; j++) { for (int k = 1; k <= 4; k++) { if (j - k >= 0) { dp[i][j] += dp[i - 1][j - k]; } } } } } int main() { init(); LL n, a, b; scanf("%lld", &n); // 没买彩票不可能亏,特判一下 if (n == 0) { puts("1/1"); return 0; } a = b = 0; for (int i = n; i <= 4 * n; i++) { b += dp[n][i]; } for (int i = 3 * n; i <= 4 * n; i++) { a += dp[n][i]; } LL gcd = __gcd(a, b); printf("%lld/%lld\n", a / gcd, b / gcd); return 0; }
D .小A的位运算
sol:记录每个二进制位出现的次数,只有只出现一次的二进制位才有可能被影响到,然后再找最小的影响。看官方题解上说的用公共前缀后缀的方法也挺有意思的,补了一下
- 记录每个二进制位出现的次数
#include "bits/stdc++.h" using namespace std; const int MAXN = 5e6 + 5; const int INF = 0x3f3f3f3f; int bit[35]; int arr[MAXN]; int main() { int n, k; int ans = 0, mn = INF; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &k); arr[i] = k; ans |= k; for (int i = 0; k; i++) { bit[i] += (k & 1); k >>= 1; } } for (int i = 0; i < n; i++) { int k = 0; for (int j = 0; j < 32; j++) { if (bit[j] == 1 && (arr[i] & (1 << j))) { k |= (1 << j); } } mn = min(mn, k); } ans -= mn; printf("%d\n", ans); return 0; }
- 公共前缀后缀
#include "bits/stdc++.h" using namespace std; const int MAXN = 5e6 + 5; const int INF = 0x3f3f3f3f; int arr[MAXN], pre[MAXN]; int suf, ans, n; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &arr[i]); pre[i] = arr[i] | pre[i - 1]; } for (int i = n; i >= 1; i--) { ans = max(ans, pre[i - 1] | suf); suf |= arr[i]; } printf("%d\n", ans); return 0; }
F .小A的最短路
sol:这应该是本场比赛我收获最大的题了,有难度。求多源最短路但不能用Floyd,感觉题目也有点描述不当没讲清楚这是一棵树。突破口在于权重都为1,只有一个为0,用最短路算法+树的最低公共祖先
- 一开始敲的超时代码
#include "bits/stdc++.h" using namespace std; typedef pair<int, int> PII; const int MAXN = 3e5 + 5; const int INF = 0x3f3f3f3f; int n, q; vector
vc[MAXN]; PII arr[MAXN]; int dis[MAXN]; bool use[MAXN]; // 构造树,arr[i].first表示i节点的父节点,arr[i].second表示i节点深度 void dfs(int k, int fa) { arr[k] = {fa, arr[fa].second + 1}; for (int i = 0; i < vc[k].size(); i++) { int j = vc[k][i].first; if (j != fa) dfs(j, k); } } // 地杰斯特拉堆优化求最短路 void dij(int s) { priority_queue , greater > que; memset(dis, INF, sizeof(dis)); que.push({0, s}); dis[s] = 0; while (!que.empty()) { int v = que.top().second; que.pop(); if (use[v] == true) { continue; } else { use[v] = true; } for (int i = 0; i < vc[v].size(); i++) { PII p = vc[v][i]; if (dis[v] + p.second < dis[p.first]) { dis[p.first] = dis[v] + p.second; que.push({dis[p.first], p.first}); } } } } // 通过不断往上找,找到最低公共祖先 int solve(int u, int v) { int du = arr[u].second; int dv = arr[v].second; int d = min(arr[u].second, arr[v].second); while (arr[du].second != d) du = arr[du].first; while (arr[dv].second != d) dv = arr[dv].first; while (du != dv) { --d; du = arr[du].first; dv = arr[dv].first; } return arr[u].second + arr[v].second - 2 * d; } int main() { int u, v; scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d%d", &u, &v); vc[u].push_back({v, 1}); vc[v].push_back({u, 1}); } dfs(1, 0); scanf("%d%d", &u, &v); vc[u].push_back({v, 0}); vc[v].push_back({u, 0}); dij(u); scanf("%d", &q); while (q--) { scanf("%d%d", &u, &v); // 要么经过缆车用从缆车出发得到的最短路,或者不用缆车用树的最低公共祖先到达两点的距离和 printf("%d\n", min(dis[u] + dis[v], solve(u, v))); } return 0; } 比赛的时候,没做出来,晚上躺床上想觉得和树的最低公共祖先有关。第二天看了题解的思路之后结果还是只能敲出超时代码。而且想了好久才明白哪里超时了。
- 理解标程之后敲的代码
#include "bits/stdc++.h" using namespace std; typedef pair<int, int> PII; const int MAXN = 3e5 + 5; const int INF = 0x3f3f3f3f; int n, q; vector
vc[MAXN]; // 有点像树状数组,fa[i][j]表示i节点前的第(1 << j)代祖先 int dis[MAXN], dep[MAXN], fa[MAXN][32]; bool use[MAXN]; void dfs(int k, int f) { dep[k] = dep[f] + 1; fa[k][0] = f; for (int i = 1; (1 << i) <= (dep[f] + 1); i++) fa[k][i] = fa[fa[k][i - 1]][i - 1]; for (int i = 0; i < vc[k].size(); i++) { int j = vc[k][i].first; if (j != f) dfs(j, k); } } void dij(int s) { priority_queue , greater > que; memset(dis, INF, sizeof(dis)); que.push({0, s}); dis[s] = 0; while (!que.empty()) { int v = que.top().second; que.pop(); if (use[v] == true) { continue; } else { use[v] = true; } for (int i = 0; i < vc[v].size(); i++) { PII p = vc[v][i]; if (dis[v] + p.second < dis[p.first]) { dis[p.first] = dis[v] + p.second; que.push({dis[p.first], p.first}); } } } } int solve(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = 20; i >= 0; i--) if ((1 << i) <= dep[u] - dep[v]) u = fa[u][i]; for (int i = 20; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return (u == v) ? dep[u] : dep[fa[u][0]]; } int main() { int u, v; scanf("%d", &n); for (int i = 1; i < n; i++) { scanf("%d%d", &u, &v); vc[u].push_back({v, 1}); vc[v].push_back({u, 1}); } dfs(1, 0); scanf("%d%d", &u, &v); vc[u].push_back({v, 0}); vc[v].push_back({u, 0}); dij(u); scanf("%d", &q); while (q--) { scanf("%d%d", &u, &v); printf("%d\n", min(dis[u] + dis[v], dep[u] + dep[v] - 2 * solve(u, v))); } return 0; }
I .小A取石子
sol:尼姆博弈变种,基本上没怎么变,从第i堆中取走k就是用arr[i] - k代替arr[i]。官方题解中说的:“判断一下小A是否能够取石子,当且仅当小A原本就是必败态并且不能取出任何石子的情形下,小A会输否则小A都会赢”更加巧妙,稍微想一下就能明白为什么;
- 比赛的时候想到的低端代码
#include "bits/stdc++.h" using namespace std; const int MAXN = 1e5 + 5; int arr[MAXN]; int main() { int n, k, x = 0; scanf("%d%d", &n, &k); for (int i = 0; i < n; i++) { scanf("%d", &arr[i]); x ^= arr[i]; } for (int i = 0; i < n; i++) { if (arr[i] >= k && (x ^ arr[i] ^ (arr[i] - k))) { puts("YES"); return 0; } } if (x) { puts("YES"); } else { puts("NO"); } return 0; }
- 更优秀的代码
#include "bits/stdc++.h" using namespace std; const int MAXN = 1e5 + 5; int arr[MAXN]; int main() { int n, k, x = 0; scanf("%d%d", &n, &k); for (int i = 0; i < n; i++) { scanf("%d", &arr[i]); if (k && k <= arr[i]) { puts("YES"); return 0; } x ^= arr[i]; } if (x) { puts("YES"); } else { puts("NO"); } return 0; }
没注意k的范围又wa了一次
J .小A的数学题
sol:官方的题解代码看不明白,但是思路了解了。看到一种倒着求的方法挺好懂的
- 听说叫做容斥,还没学过
#include "bits/stdc++.h" using namespace std; typedef long long LL; const int MAXN = 1e6 + 5; const int MOD = 1e9 + 7; // f[i]表示gcd == i的情况出现的次数 LL f[MAXN], ans; int main() { int n, m; scanf("%d%d", &n, &m); if (n > m) swap(n, m); for (int i = n; i >= 1; i--) { f[i] = 1LL * (n / i) * (m / i); for (int j = i << 1; j <= n; j += i) f[i] -= f[j]; f[i] %= MOD; ans = (ans + i * f[i] * i) % MOD; } printf("%lld\n", ans); return 0; }