1001 - Alice Game
题目大意
有一个长度为 n n n 的怪物序列( n n n 可能为 0 0 0),给定一个 k k k,轮到某人回合时会有两种操作:
-
选择一个连续的序列,它的长度小于等于 k k k,将其全部删除
-
选择一个连续的序列,将其中连续的 k k k 个数删除,使删除后原序列恰好变成两个非空序列
Alice和Bob轮流操作,问谁会赢
解题思路
先进行小数据推导:
-
1 ≤ n ≤ k 1\le n\le k 1≤n≤k 时,一次操作直接搞定,Alice赢
-
n = 0 n=0 n=0 或者 n = k + 1 n=k+1 n=k+1 时,先手无法操作,Bob赢
-
2 k + 2 ≤ n ≤ 3 k + 1 2k+2\le n\le 3k+1 2k+2≤n≤3k+1 时,先手可以自己决定是否在此区间内获胜
若剩下的区间存在先手必败状态便会改变总游戏的先手胜负状态
因此Alice为了赢会尽量不把2和3的情况留给Bob
当 n = 5 k + 3 n=5k+3 n=5k+3 时,Alice无论如何操作总会留下让Bob可以改变总游戏先手胜负状态的区间,因此必败
以此类推,可以发现当 n % ( 4 k + 2 ) = k + 1 n\%(4k+2)=k+1 n%(4k+2)=k+1 时Bob获
其余状态就是Alice获胜
code
#include <bits/stdc++.h>
using namespace std;
int t, k, n;
int main() {
scanf("%d", &t);
while (t --) {
scanf("%d%d", &k, &n);
if (n % (4 * k + 2) == k + 1 || n == 0) printf("Bob\n");
else printf("Alice\n");
}
return 0;
}
1011 - SPY finding NPY
题目大意
将 1 1 1 到 n n n 随机排序,你需要确定一个下标 k k k 进行以下操作( 0 ≤ k < n 0\le k<n 0≤k<n):
-
取前 k k k 个数中的最大数记为 x x x,如果 k = 0 k=0 k=0,那么 x = − 1 x=-1 x=−1
-
在 k + 1 k+1 k+1 到 n − 1 n-1 n−1 中去枚举,找到第一个大于 x x x 的数
-
如果第二步没有找到这个数,取第 n n n 个数
问你如何取 k k k 才能使最后取的数为 n n n 的概率最大
解题思路
拿到这道题第一感觉是道数学题,所以先推公式
所求概率等于合法方案数除以总方案数,所以我们只要推合法方案数即可
在 n n n 一定的情况下对于每个 k k k, k = 0 k=0 k=0 时方案数为 ( n − 1 ) ! (n-1)! (n−1)!
k ≠ 0 k\ne0 k=0 时,假设 i i i 是 n n n 这个数的位置, j j j 是前 k k k 个数中最大的数
如果暴力求解,可以计算出合法方案数为 k ∑ i = k + 1 n ∑ j = i − 1 n − 1 A j − 1 i − 2 A n − i n − i k\sum_{i=k+1}^n\sum_{j=i-1}^{n-1}A_{j-1}^{i-2}A_{n-i}^{n-i} k∑i=k+1n∑j=i−1n−1Aj−1i−2An−in−i
解释: j j j 可以放在 k k k 个位置,因此有 k k k 个方案
再用 1 1 1 到 j − 1 j-1 j−1 去填补前 i i i 位,空位有 i − 2 i-2 i−2 个,因此有 A j − 1 i − 2 A_{j-1}^{i-2} Aj−1i−2 个方案
最后还剩下 n − i n-i n−i 个位置要用剩下 n − i n-i n−i 个数去填,因此有 A n − i n − i A_{n-i}^{n-i} An−in−i 个方案
然后我就卡住了,因为我不会化简,所以就去打一下 n = 4 n=4 n=4 的情况
打表结果如下:
用自己的式子计算如下:
然后发现好像和自然对数有关系,找下规律,猜想
k
∑
i
=
k
+
1
n
∑
j
=
i
−
1
n
−
1
A
j
−
1
i
−
2
A
n
−
i
n
−
i
=
k
∑
i
=
k
n
−
1
(
n
−
1
)
!
i
k\sum_{i=k+1}^n\sum_{j=i-1}^{n-1}A_{j-1}^{i-2}A_{n-i}^{n-i}=k\sum_{i=k}^{n-1}\frac{(n-1)!}{i}
k∑i=k+1n∑j=i−1n−1Aj−1i−2An−in−i=k∑i=kn−1i(n−1)!
在考场上直接跳过验证进入了下一步,赛后尝试推了一下还是推不出来,然后尝试换了个思路
在确定了 n n n 所在的位置在 i i i 之后,其实只需要保证前 i − 1 i-1 i−1 个数中的最大值在 1 1 1 到 k k k 就可以了
而这种情况在全部的排列中出现的概率为 k i − 1 \frac{k}{i-1} i−1k
那么合法方案数就是 ∑ i = k + 1 n A n − 1 n − 1 ∗ k i − 1 = k ∑ i = k n − 1 ( n − 1 ) ! i \sum_{i=k+1}^{n}A_{n-1}^{n-1}*\frac{k}{i-1}=k\sum_{i=k}^{n-1}\frac{(n-1)!}{i} ∑i=k+1nAn−1n−1∗i−1k=k∑i=kn−1i(n−1)!
不难靠直觉发现这是个关于 k k k 先増后减的函数
可以三分,可以靠样例和公式判断答案在 n e \frac{n}{e} en 左右去找,甚至可以暴力
因为暴力不会超时且更简单,所以我选择了暴力
比较的时候可以把 ( n − 1 ) ! (n-1)! (n−1)! 约掉,只要比较 k ∑ i = k n − 1 1 i k\sum_{i=k}^{n-1}\frac{1}{i} k∑i=kn−1i1 就可以了
code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 9;
const double EPS = 1e-12;
int t, n, ans;
double f[N], num;
int main() {
for (int i = 1; i < N; ++ i) f[i] = f[i - 1] + (1.0 / i);
//预处理,f[i]为数列{1/n}的前i项和,要得到表达式只需要差分一下就可以了
scanf("%d", &t);
while (t --) {
scanf("%d", &n);
ans = 0, num = 1.0;//num初始值为k=0时的方案数除以(n-1)!
for (int i = 1; i < n; ++ i)
if ((f[n - 1] - f[i - 1]) * i - num <= EPS) {ans = i - 1; break;}
//找到第一个下降的位置并记录前一位即是所求最大值的位置,浅浅注意一下double比大小的精度
else num = (f[n - 1] - f[i - 1]) * i;
printf("%d\n", ans);
}
return 0;
}
1010 - Klee likes making friends
题意、思路待补
code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 9;
const int M = 2009;
int t, n, m, a[N], f[M][M], b[M][M], c[M], g[M] = {0x3f3f3f3f}, sum;
int main() {
scanf("%d", &t);
while (t --) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++ i) scanf("%d", &a[i]);
sum = 0x3f3f3f3f;
for (int i = 0; i < m; ++ i) {
f[i][0] = 0x3f3f3f3f;
for (int j = 1; j <= m; ++ j) {
if (i - j < 0) b[i][j] = 0x3f3f3f3f;
else b[i][j] = a[i] + a[i - j];
f[i][j] = min (f[i][j - 1], b[i][j]);
}
}
for (int i = m ; i < n; ++ i) {
for (int j = 1; j <= m; ++ j)
c[j] = a[i] + f[(i - j) % m][m - j],
g[j] = min(g[j - 1], c[j]);
for (int j = 1; j <= m; ++ j)
b[i % m][j] = c[j], f[i % m][j] = g[j];
}
for (int i = 1; i <= m; ++ i)
sum = min(sum, f[(n - i) % m][m - i]);
printf("%d\n", sum);
}
return 0;
}