题目链接:https://ac.nowcoder.com/acm/contest/879#question
官方题解:https://ac.nowcoder.com/discuss/189446?type=101&order=0&pos=1&page=1
A .简单计数
sol:考虑两个数组a和b,a[i]表示第i天在1号城市的方案数,b[i]表示第i天不在1号城市的方案数,可以得到如下dp方程式:
a[i] = b[i - 1]; b[i] = a[i - 1] * (n - 1) + b[i - 1] * (n - 2);
a[k]就是问题的答案。但是k比较大,不能暴力循环。可以将dp方程式转换成矩阵来利用矩阵快速幂求解
ps:n < 2的情况下上面的dp方程式就不对了。但是按照题目给出的maker.cpp,n和k的范围都在8e8到9e8之间;所以我就不得不吐槽一下这个示例1了,根本不满足数据范围。
- 矩阵快速幂
#include "bits/stdc++.h" using namespace std; const int MOD = 998244353; struct Mat { int mat[2][2]; Mat() {memset(mat, 0, sizeof(mat));} friend Mat operator * (Mat a, Mat b) { Mat ans; for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) ans.mat[i][j] = (ans.mat[i][j] + 1LL * a.mat[i][k] * b.mat[k][j]) % MOD; return ans; } }; Mat mat_pow(Mat m, int k) { Mat ans; ans.mat[0][0] = ans.mat[1][1] = 1; while (k) { if (k & 1) ans = ans * m; m = m * m; k >>= 1; } return ans; } int main() { int n, k; scanf("%d%d", &n, &k); Mat m; m.mat[0][1] = 1; m.mat[1][0] = n - 1; m.mat[1][1] = n - 2; m = mat_pow(m, k); printf("%d ", m.mat[0][0]); return 0; }
B .投硬币
sol:把k次成功到n次成功的概率算出来,然后累加
- 逆元
#include "bits/stdc++.h" using namespace std; const int MAXN = 1e5 + 5; const int MOD = 998244353; int powa[MAXN], powb[MAXN], inv[MAXN]; int ans, fac; int quick_pow(int n, int k) { int ans = 1; while (k) { if (k & 1) ans = 1LL * ans * n % MOD; n = 1LL * n * n % MOD; k >>= 1; } return ans; } int main() { // a 是成功的概率, b 是失败的概率 int n, k, a, b; scanf("%d%d%d", &n, &k, &a); b = (MOD + 1 - a) % MOD; powa[0] = powb[0] = fac = inv[0] = 1; for (int i = 1; i <= n; i++) { powa[i] = 1LL * a * powa[i - 1] % MOD; powb[i] = 1LL * b * powb[i - 1] % MOD; fac = 1LL * fac * i % MOD; } inv[n] = quick_pow(fac, MOD - 2); // 线性地推阶乘逆元 for (int i = n - 1; i > 0; i--) inv[i] = 1LL * inv[i + 1] * (i + 1) % MOD; for (int i = k; i <= n; i++) { int p = 1LL * powa[i] * powb[n - i] % MOD; // 这个c就是排列组合数,C(n, i); int c = 1LL * fac * inv[n - i] % MOD * inv[i] % MOD; ans = (ans + 1LL * p * c) % MOD; } printf("%d ", ans); return 0; }
第一次在比赛中运用逆元解出题目,而且比赛结束后又优化了代码,成功提交了目前为止本题最快代码。学习了线性递推阶乘的逆元。
C .植树造林
sol:到所有树的最远距离最小的树一定是最中间的树。所以,如果n为奇数,最中间的树有一棵,否则有两棵。
- 贪心水题
#include "bits/stdc++.h" using namespace std; int main() { int n; scanf("%d", &n); puts(n & 1 ? "1" : "2"); return 0; }
D .签到提I
sol:排个序就完事了
- 排序
#include "bits/stdc++.h" using namespace std; const int MAXN = 1e5 + 5; int arr[MAXN]; int main() { int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &arr[i]); sort(arr + 1, arr + 1 + n); printf("%d ", arr[k]); return 0; }
作为签到题,这题确实很水。不过如果把n的范围加到1e7,那么普通sort就要超时了。可以用堆排或手写的针对第k小的快排来完成。
G .many sum
sol:模拟素数筛的方法来求出b数组,然后把b数组异或起来得到答案
- 素数筛的思路
#include "bits/stdc++.h" using namespace std; const int MAXN = 2e6 + 5; int a[MAXN], b[MAXN]; int n, MOD, ans; int main() { scanf("%d%d%d", &n, &a[1], &MOD); for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j += i) b[j] = b[j] + a[i]; a[i + 1] = (a[i] + 7 * i + 7) % MOD; ans ^= b[i]; } printf("%d ", ans); return 0; }
AB两道难的我做出来了,这题简单的比赛还没做出来。完全不知道d|i是什么意思,后来素数筛又算错了复杂度以为要超时。还是要仔细啊。