2021牛客暑期多校训练营1 个人补题记录
比赛链接:Here
A - Alice and Bob (Game,打表)
emmm,博弈签到题
题意:
Alice(先手) 和 Bob 面前有两堆石头,石头数量为 \(n\) 和 \(m\)。
每次操作可从一堆石头中取出 \(k\) 块石头,在另一堆石头中取出 \(s\times k\) 块。
哪一方无法执行操作则判负
我们知道当面临两堆石头数量为(0,0)时为失败,那么如果能一次操作能取光石头此时为必胜态
我们用一个二维数组 \(f[i][j]\) 来表示第一堆石头数量为i第二堆石头数量为j的情况,\(f[i][j]=1\) 表示状态面临两堆石头数量为 \(i,j\) 时可以一步取光石头,\(f[i][j]=0\) 表示状态面临两堆石头数量为 \(i,j\) 时无法一次取光石头。初始 \(f[0][0]=0\) ,根据题目 \(f[k][s*k]\) 或 \(f[s*k][k]\) 一定能够一次性取完,我们可以从(\(i=0,j=0\) )开始自小变大枚举 \(s\) 与 \(k\) 找出所有先手必胜的状态,由于是自小变大枚举所以发现有\(f[i][j]=0\) 就发现了一个必败的局面再对它枚举 \(s\) 与 \(k\) ,当初始状态 \(f[i][j]=1\) 时 Alice 显然获胜,那么现在面临的问题是当初始状态为 \(f[i][j]=0\) 时 Alice 能否获胜,由于 Alice 一次取不完石头,又要保证自己一次取完后不输,即需要满足取完石子后 \(f[i-k][j-s*k]=0\) 或者 \(f[i-s*k][j-k]=0\),这显然是不可能的,因为对于每一个 \(f[i][j]==0\) 其 \(f[i+k][j+s*k]=1\)(同 \(f[0][0]\) ),时间复杂度为\(O(n^4)\),显然不满足题目数据,但是这里大家需要知道一个结论:对于一个的 \(i\) 只存在至多一种 \(j\) 后手能够获胜
证明如下:
若存在 \((i,q)(i,p)\)(p>q)满足后手胜,那么Alice只需将(i,p)->(i,q)即可获胜,不满足后手胜的条件。
这样我们的时间复杂度大约在 \(O(n^3)\),本题数据量为 \(5000\),勉勉强强过的去,但是需要避免cin
等操作,数组也需要开成 bool
的来节省运算时间。
【AC Code】
#include<iostream>
#include<stdio.h>
using namespace std;
bool f[5010][5010] = {false};
int main() {
// cin.tie(nullptr)->sync_with_stdio(false);
//自小到大枚举i,j
for (int i = 0; i <= 5000; i++)
for (int j = 0; j <= 5000; j++) {
if (f[i][j] == 0) { //对于每种必败态进行拓展
//f[i][j]与f[j][i]是等价的
for (int k = 1; k + i <= 5000; k++)for (int s = 0; s * k + j <= 5000; s++)f[i + k][j + s * k] = 1;
for (int k = 1; k + j <= 5000; k++)for (int s = 0; s * k + i <= 5000; s++)f[i + s * k][j + k] = 1;
}
}
int n, m, t;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
if (!f[n][m])puts("Bob");
else puts("Alice");
}
}
看了下其他人的代码,个个打表也是神了
B - Ball Dropping (Math)
给一个中间空的等腰梯形。
然后有一个圆,问是否能从中间穿过梯形。
借用 纯神 的说法:
这道题是一个数学题,简单画个图即可
看到梯形我们考虑用相似三角。
首先两个相似求出 \(\beta: \frac{b}{a} = \frac{\beta}{h + \beta}\)
\(\beta = \frac{bh}{a - b}\)
然后由于它是等腰,我们可以继续求。
(注意这个球掉到上面的地方与墙面碰到的两个点组成的直线不是直径)
(我当时就搞错了,搞了半天才发现,两个墙壁是它的切线)
用勾股可以得到 \(Side = \sqrt{(a/2)^2 + (h + \beta)^2}\)
然后根据相似,可以得到 \(\beta + ans\)
\(a/2=\frac{Side}{ans + \beta + ans}\\ans+\beta = \frac{r * Side}{a/2}\\ans = \frac{r * Side}{a/2} - \beta\)
【AC Code】
using ld = long double;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
ld r, a, b, h;
scanf("%Lf %Lf %Lf %Lf", &r, &a, &b, &h);
if (a < b) swap(a, b);
if ((r * 2.0) <= b) printf("Drop");
else {
printf("Stuck\n");
ld beta = b * h / (a - b);
ld side = sqrt((a / 2) * (a / 2) + (beta + h) * (beta + h));
printf("%Lf", side * r / (a / 2) - beta);
}
}
C - Cut the Tree
待补
D - Determine the Photo Position (签到)
题意:
给出一个矩阵,问有多少个地方有连续的 \(x\) 个 \(0\),一定要在一行中。
暴力模拟枚举,
记录一个 \(num\) 为当前这一行最后出现了多少个连续的 \(0\),那如果接下来是 \(0\),就 \(num\) 加一,否则就变成 \(0\)。
然后每次搞完看一下,如果 \(num\) 大于等于 \(m\) 就答案加一,表示以这个点结束的一个位置是连续的 \(x\) 个 \(0\)。
【AC Code】
int main() {
// cin.tie(nullptr)->sync_with_stdio(false);
int n, m;
cin >> n >> m;
int a[n + 1][n + 1], ans = 0;
for (int i = 1; i <= n; ++i) {
int num = 0;
for (int j = 1; j <= n; ++j) {
scanf("%1d", &a[i][j]);
if (a[i][j] == 0)num++;
else num = 0;
if (num >= m)ans++;
}
}
cout << ans << "\n";
}
E - Escape along Water Pipe
待补
F - Find 3-friendly Integers (签到)
题意:
3-friendly定义:一个正整数的各个位数能整除 3 则称这个数为 3-friendly.
比如,104中 0 可整除 3,124中 12 可整除 3
问在区间 \(【L,R】\) 中有多少个 3-friendly.
这个很容易知道三位数以上的值随便组合都能整除 3
所以我们可以先序找出 1 ~ 100 中所有非 3-friendly 的数,然后在区间计数时删去即可
【AC Code】记得使用 long long
using ll = long long;
int vis[] = {1, 2, 4, 5, 7, 8, 11, 14, 17, 22, 25, 28, 41, 44, 47, 52, 55, 58, 71, 74, 77, 82, 85, 88};
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int _; for (cin >> _; _--;) {
ll l, r, k = 0;
cin >> l >> r;
for (int i = 0; i < 24; ++i) {
if (vis[i] >= l and vis[i] <= r)k++;
}
cout << r - l + 1 - k << "\n";
}
}
G - Game of Swapping Numbers
题意:
给两个数组a,b,现在可以交换a中的数k次,求 \(\sum\limits_{i = 1}^n|a_i - b_i|\)的最大值。
首先我们思考一下什么样的两个数交换后的结果会大:
假如有两对数: \((a_1,b_1),(a_2,b_2)\)
原结果为 \(abs(a_1-b_1)+abs(a_2-b_2)\)
- 如果 \(a_1>b_1\) && $a_2>b_2 $&& \(b_1>a_2\) :
那么原结果为 \(abs(a_1-b_1)+abs(a_2-b_2)=a_1+a_2-b_1-b_2\)
交换后结果为:\(abs(a_1-b_1)+abs(a_2-b_2)=a_1-b_2+b_1-a_2=a_1+b_1-a_2-b_2\)
结果之差为 \(2 * b_1 - 2 * a_2=2*min(a_1,b_1)-2 * max(a_2,b_2)\) - 如果 \(a_1>b_1\) && \(a_2>b_2\) && \(b_1 < a_2\) :
那么原结果为 \(abs(a_1-b_1)+abs(a_2-b_2)=a_1+a_2-b_1-b_2\)
交换后结果为:\(abs(a_1-b_2)+abs(a_2-b_1)=a_1-b_2+a_2-b_1=a_1+a_2-b_1-b_2\)
结果之差为 \(0\)
如此分析下去:
我们发现可以使结果增长的情况为一对的最小值大于另一对的最大值时.
然后当 \(n> 2\) 时,最优解结果\((a1,b1),(a2,b2),(a3,b3).........\)其中 \(a_i<b_i\) 或者 \(a_i>b_i\) 的对数一定有一种有两对,假如这两对为$(a1,b1),(a2,b2) $ {\(a_1>b_1\) && \(a_2>b_2\)}如上分析要么增加要么不变,所以恰好为 \(k\) 在 \(n>2\) 时等价于小于等于 \(k\) 次,所以加上可以增加的情况并小于等于 \(k\) 次即可。
【AC Code】
const int N = 5e5 + 7;
ll a[N], b[N], Ma[N], Mi[N];
bool cmp(ll a, ll b) { return a > b;}
int main() {
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
ll ans = 0;
if (n == 2) {
if (k % 2) swap(a[1], a[2]);
ans = ans + abs(a[1] - b[1]) + abs(a[2] - b[2]);
} else {
for (int i = 1; i <= n; i++) {
ans = ans + abs(a[i] - b[i]);
Ma[i] = max(a[i], b[i]);
Mi[i] = min(a[i], b[i]);
}
sort(Mi + 1, Mi + n + 1, cmp);
sort(Ma + 1, Ma + n + 1);
for (int i = 1; i <= k && i <= n; i++) {
if (Mi[i] > Ma[i]) ans = ans + 2 * (Mi[i] - Ma[i]);
else break;
}
}
cout << ans << endl;
return 0;
}
H - Hash Function
待补
I - Increasing Subsequence (DP)
题意:
给定一个长度为 \(n(n<=5000)\) 的排列,两个人轮流从这个序列中选择一个数,要求当前回合此人选择的数大于任意一个已经被选择的数,并且该数在数组中的位置 \(i\) 与此人上一次选择的数在数组中的位置 \(j\) 要满足 \(i>j\),如果有多个数合法则等概率的从这些数中选一个。当没有合法数时结束,问最终被选择的数的期望个数。
思路:
期望 \(dp\) 问题,设 \(dp[x][y]\) 为当前轮到此人选数并且他上一次选了数 \(x\) ,另一个人选了数 \(y\) 开始游戏到游戏结束时的数期望个数,则 \(dp[x][y] = inv[tot] * \sum\limits_{t=1}^{tot} + 1\), \(tot\) 为可选择的数字个数,\(a_i\) 为可选的数字。首先枚举 \(y\) ,然后用前缀和处理即可 \(\mathcal{O}(1)\) 完成转移,再枚举 \(x\) ,总复杂度为 \(\mathcal{O}(n^2)\) 。
【AC Code】
const int mod = 998244353, N = 5e3 + 10;
int p[N], c[N], sum[N], pos[N], inv[N];
int dp[N][N];
int qpow(int a, int b) {
int ans = 1;
for (; b; b >>= 1, a = 1ll * a * a % mod)
if (b & 1)ans = 1ll * ans * a % mod;
return ans;
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n;
cin >> n;
for (int i = 1; i <= n; i += 1) {
cin >> p[i];
pos[p[i]] = i;
inv[i] = qpow(i, mod - 2);
}
for (int j = n; j > 0; j -= 1) {
for (int i = 0; i <= n; i += 1) c[i] = sum[i] = 0;
for (int t = j + 1; t <= n; t += 1) {
c[pos[t]] = 1;
sum[pos[t]] = dp[j][t];
}
for (int i = n - 1; i >= 0; i -= 1) {
c[i] += c[i + 1];
sum[i] = (sum[i] + sum[i + 1]) % mod;
}
for (int i = j - 1; i >= 0; i -= 1) {
int tot = c[pos[i]];
int sm = sum[pos[i]];
if (tot) dp[i][j] = (1ll * inv[tot] * sm + 1) % mod;
}
}
ll ans = 0;
for (int i = 1; i <= n; i += 1)ans = (ans + dp[0][i]) % mod;
cout << (1ll * ans * inv[n] % mod + 1) % mod;
}
J - Journey among Railway Stations
待补
K - Knowledge Test about Match
待补