SCUACM22暑假集训前劝退赛部分题解
Posted hans774882968
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SCUACM22暑假集训前劝退赛部分题解相关的知识,希望对你有一定的参考价值。
文章目录
传送门
作者:hans774882968以及hans774882968
A
简单几何题,用三角函数知识很容易推(对于考上SCU的同学)。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 1e6 + 5;
int r, a, b, h;
void dbg()
puts ("");
template<typename T, typename... R>void dbg (const T &f, const R &... r)
cout << f << " ";
dbg (r...);
template<typename Type>inline void read (Type &xx)
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
void read()
template<typename T, typename ...R>void read (T &x, R &...r)
read (x);
read (r...);
int main()
read (r, a, b, h);
if (2 * r <= b)
puts ("Drop");
return 0;
double H = 1.0 * h * a / (a - b);
double theta = atan (1.0 * a / 2 / H);
printf ("Stuck\\n%.10lf\\n", r / sin (theta) - b / (2 * tan (theta) ) );
return 0;
B
一看就是博弈dp。先打个表
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 5e3 + 5;
int dp[N][N], a[N];
int n, m;
void dbg()
puts ("");
template<typename T, typename... R>void dbg (const T &f, const R &... r)
cout << f << " ";
dbg (r...);
template<typename Type>inline void read (Type &xx)
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
void read()
template<typename T, typename ...R>void read (T &x, R &...r)
read (x);
read (r...);
bool dfs (int x, int y)
if (~dp[x][y]) return dp[x][y];
dp[x][y] = false;
if (! (x + y) ) return dp[x][y] = false;
if (!x || !y) return dp[x][y] = true;
if (x == y) return dp[x][y] = true;
if (x < y) return dp[x][y] = dfs (y, x);
rep (k1, 1, x)
for (int k2 = 0; k2 <= y; k2 += k1)
dp[x][y] |= !dfs (x - k1, y - k2);
if (dp[x][y]) break;
rep (k1, 1, y)
for (int k2 = 0; k2 <= x; k2 += k1)
dp[x][y] |= !dfs (x - k2, y - k1);
if (dp[x][y]) break;
return dp[x][y];
void bf (int n)
memset (dp, -1, sizeof dp);
rep (i, 0, n) rep (j, 0, n) dfs (i, j);
// rep (i, 0, n) rep (j, 0, n) cout << dp[i][j] << " \\n"[j == n];
rep (i, 0, n) rep (j, 0, n) if (!dp[i][j]) dbg (i, j);
puts ("");
rep (i, 0, n) rep (j, 0, n) if (!dp[i][j] && i < j) dbg (i, j);
int main()
bf (300);
// memset (dp, -1, sizeof dp);
// int lim = 500;
// rep (i, 1, lim)
// if (a[i]) continue;
// rep (j, i + 1, lim)
// if (!dfs (i, j) )
// a[i] = j;
// break;
//
//
// if (a[i]) a[a[i]] = i;
// if (i % 100 == 0) cout << "i=" << i << endl; //dbg
//
// rep (i, 1, lim) cout << a[i] << " \\n"[i == lim];
return 0;
发现每一行至多一个false,但没有发现其他规律。因此有一种做法是跑上面那段被注释的代码,把每一行的false的点(可能不存在)给找出来。但是要等很久,算了。
正解是:既然为false的点不超过5000,那么就直接用刷表法。根据博弈dp常识,值为true的点推不出任何点,值为false的点可以推出一系列值为true的点。根据调和级数,复杂度O(n^2*logn)
。说实话这是我第一次看到这种套路。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 5e3 + 5;
bool dp[N][N];
int n, m;
void dbg()
puts ("");
template<typename T, typename... R>void dbg (const T &f, const R &... r)
cout << f << " ";
dbg (r...);
template<typename Type>inline void read (Type &xx)
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';
xx *= f;
void read()
template<typename T, typename ...R>void read (T &x, R &...r)
read (x);
read (r...);
void init (int n)
rep (i, 0, n) rep (j, 0, n)
if (dp[i][j]) continue;
rep (k1, 1, n - i)
for (int k2 = 0; j + k2 <= n; k2 += k1)
dp[i + k1][j + k2] = true;
rep (k1, 1, n - j)
for (int k2 = 0; i + k2 <= n; k2 += k1)
dp[i + k2][j + k1] = true;
int main()
init (5000);
int T;
read (T);
while (T--)
read (n, m);
puts (dp[n][m] ? "Alice" : "Bob");
return 0;
E
设dp[i]
为当前得到的数集的最大值为i
,能走的步数的期望。再设ans[i]
表示当前得到的数集的最大值为i
,能获得的分数的期望。因为题目中,分数只获得1次,所以我们假设当前存在一个数集的最大值,但并没有拿过分数。显然这一构造性假设是合法的。
没有数,就用“有一个0”来表示,于是ans[0]
即所求。
值得注意的是,我们也可以用数学形式E(x[i])
和E(x[i] ^ 2)
来表示dp[i]
和ans[i]
。
推dp
全期望公式:E(X) = sum(E(X|Y = y[i]) * P(Y = y[i]))
设下一个数为j
。当下一个数小于i
,能走的步数为1;大于等于i
,能走的步数为E(1 + x[j]) = 1 + dp[j]
。故由全期望公式:
dp[i] = sum(p[j])(j < i) + sum(p[j] * (1 + dp[j]))(j >= i)
移项得:
dp[i] = (1 + sum(p[j] * dp[j])) / (1 - p[i]) (j > i)
推ans
当下一个数小于i
,能获得的分数为1。大于等于i
,由上述数学形式得E((1 + x[j]) ^ 2) = E(x[j] ^ 2) + 2 * E(x[j]) + 1 = ans[j] + 2 * dp[j] + 1
。故由全期望公式:
ans[i] = sum(p[j])(j < i) + sum(p[j] * (ans[j] + 2 * dp[j] + 1))(j >= i)
移项得:
ans[i] = (1 + 2 * dp[i] * p[i] + sum(p[j] * (ans[j] + 2 * dp[j]))) / (1 - p[i]) (j > i)
总结:关注期望dp的数学记号!
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)
const int N = 5e3 + 5;
const int mod = 998244353;
LL dp[N], ans[N], p[N];
int a[N];
int n;
void dbg()
puts ("");
template<typename T, typename... R>void dbg (const T &f, const R &... r)
cout << f << " ";
dbg (r...);
template<typename Type>inline void read (Type &xx)
Type f = 1;
char ch;
xx = 0;
for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx 2021SCUACM集训队冬季选拔2大部分题解