《信息学奥赛一本通·高手专项训练》集训 Day 5
luckydrawbox · · 个人记录
我博弈论和概率论能力为
博弈论
\color{Black}\text{A. 染色游戏}
题目
Alice 和 Bob 在一棵有根树上玩一个游戏。初始状态下这棵树的每个结点被染成了黑色或者白色。
游戏时两人反复轮流进行以下操作:选一个目前仍然为白色的结点,将这个点以及它到根的路径上的所有点都染为黑色。当前的操作结束后整棵树都变黑的一方胜利。
先手的 Alice 想知道自己是否存在必胜策略,如果是,她进而希望知道在确保胜利的前提下第一步可以采取的行动。
题解
SP11414 COT3 - Combat on a tree
设
也就是说,求
- 一个森林内所有树的
SG 值的异或和\rightarrow 该森林的SG 值; - 所有可能构成的森林的
SG 值取mex\rightarrow SG(x) 。
所有可能构成的森林的
考虑用数据结构来优化这个过程,每个节点上都用一个
最后再
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 2e6 + 10;
int n, c[N], sg[N], ans[N], cnt, rt[N], ch[N << 1][2], tag[N << 1], cov[N << 1];
int head[N], ver[N << 1], nxt[N << 1], tot;
void add(int x, int y) {
ver[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void pushtag(int p, int k, int x) {
if (k == -1)
return;
if ((x >> k) & 1)
swap(ch[p][0], ch[p][1]);
tag[p] ^= x;
}
void pushdown(int p, int k) {
if (!tag[p])
return;
pushtag(ch[p][0], k - 1, tag[p]);
pushtag(ch[p][1], k - 1, tag[p]);
tag[p] = 0;
}
void insert(int &p, int k, int x) {
if (!p)
p = ++tot;
if (k == -1) {
cov[p] = 1;
return;
}
insert(ch[p][x >> k & 1], k - 1, x);
cov[p] = cov[ch[p][0]] & cov[ch[p][1]];
}
int merge(int x, int y, int k) {
if (!x || !y)
return x + y;
if (k == -1) {
cov[x] |= cov[y];
return x;
}
pushdown(x, k);
pushdown(y, k);
ch[x][0] = merge(ch[x][0], ch[y][0], k - 1);
ch[x][1] = merge(ch[x][1], ch[y][1], k - 1);
cov[x] = cov[ch[x][0]] & cov[ch[x][1]];
return x;
}
int mex(int p, int k) {
if (!p || k == -1)
return 0;
pushdown(p, k);
if (cov[ch[p][0]])
return (1 << k) | mex(ch[p][1], k - 1);
return mex(ch[p][0], k - 1);
}
void dfs1(int x, int fa) {
int v = 0;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
dfs1(y, x);
v ^= sg[y];
}
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
pushtag(rt[y], 20, v ^ sg[y]);
rt[x] = merge(rt[x], rt[y], 20);
}
if (!c[x])
insert(rt[x], 20, v);
sg[x] = mex(rt[x], 20);
}
void dfs2(int x, int fa, int v) {
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
v ^= sg[y];
}
if (!v && !c[x])
ans[++cnt] = x;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
dfs2(y, x, v ^ sg[y]);
}
}
int main() {
n = read();
for (int i = 1; i <= n; i++) {
c[i] = read();
}
for (int i = 1; i < n; i++) {
int u, v;
u = read();
v = read();
add(u, v);
add(v, u);
}
dfs1(1, 0);
if (!sg[1]) {
puts("-1");
return 0;
}
dfs2(1, 0, 0);
sort(ans + 1, ans + cnt + 1);
for (int i = 1; i <= cnt; i++) {
write(ans[i]);
putchar('\n');
}
return 0;
}
\color{#52C41A}\text{B. 日历游戏}
题目
小 A 和 TA 的宠物正在玩一个日历游戏,开始时,他们从 1900 年 1 月 1 日到 2012 年 12 月 22 日选一个日期开始,依次按照如下规则之一向后跳日期:
跳到日历上的下一天。 跳到日历上的下个月的同一天(如果不存在,则不能这么做)。 要是谁正好到达 2012 年 12 月 22 日那么他就赢了,如果到达这天之后的日期那他就输了。
每次都是小 A 先走的。
现在,给你一个日期,请问小 A 一定能赢吗?
题解
考虑计算日期
日期要写对,不然会像我一样得到玄学分数。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 6e4 + 10;
int month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, tot, f[N];
struct asdf {
int y, m, d;
vector<int> nxt;
} a[N], e, g;
map<int, int> has;
bool pd(asdf x) {
if (x.y < 1900 || x.y > 2012)
return 0;
if (x.m < 1 || x.m > 12)
return 0;
if (x.d < 1 || x.d > 31)
return 0;
if (x.y == 2012) {
if (x.m > 12)
return 0;
if (x.m == 12 && x.d > 22)
return 0;
}
if (x.m == 2) {
if ((x.y % 400 == 0) || (x.y % 100 != 0 && x.y % 4 == 0))
return x.d <= 29;
return x.d <= 28;
}
return x.d <= month[x.m];
}
int dfs(int x) {
if (f[x] != -1)
return f[x];
for (int i = 0; i <= 3; i++) {
bool ff = 1;
for (int j = 0; j < a[x].nxt.size(); j++) {
if ((dfs(has[a[x].nxt[j]])) == i)
ff = 0;
}
if (ff)
return f[x] = i;
}
}
int main() {
for (e.y = 1900; e.y <= 2012; e.y++) {
for (e.m = 1; e.m <= 12; e.m++) {
for (e.d = 1; e.d <= 31; e.d++) {
if (!pd(e))
continue;
has[e.y * 10000 + e.m * 100 + e.d] = ++tot;
a[tot] = e;
g = e;
g.d++;
if (g.m == 12 && g.d == 32) {
g.y++;
g.m = 1;
g.d = 1;
} else if (!pd(g)) {
g.m++;
g.d = 1;
}
if (pd(g))
a[tot].nxt.push_back(g.y * 10000 + g.m * 100 + g.d);
g = e;
g.m++;
if (g.m > 12) {
g.y++;
g.m = 1;
}
if (pd(g))
a[tot].nxt.push_back(g.y * 10000 + g.m * 100 + g.d);
}
}
}
memset(f, -1, sizeof(f));
f[has[2012 * 10000 + 12 * 100 + 22]] = 0;
int Y, M, D;
while (cin >> Y && Y) {
M = read();
D = read();
cout << (dfs(has[Y * 10000 + M * 100 + D]) == 0 ? "NO" : "YES") << endl;
}
return 0;
}
\color{#52C41A}\text{C. 棋盘游戏}
题目
Alice 和 Bob 在玩一个游戏,给出一张
题解
将棋子黑白染色,棋盘就变成了一张二分图,对它求最大匹配。
如果一个点不一定在最大匹配中,那么这个点 Alice 必胜。证明:任选一个这个点未匹配的最大匹配,Alice 只要一直走匹配边,Bob 就只能走非匹配边,如果 Bob 获胜说明找到了一条增广路,矛盾。
如果一个点一定在最大匹配中,那么这个点 Bob 必胜。证明:任选一个最大匹配,Bob 只要一直走匹配边,Alice 就只能走非匹配边,如果 Alice 获胜就可以构造一个这个点未匹配的最大匹配,矛盾。
所以判断每个点是否一定在最大匹配里即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 110, M = 1e5 + 10;
int n, m, a[N][N], v[M], match[M], vis[M], nid, z[M], t;
int head[M], ver[M << 1], nxt[M << 1], tot;
int fx[4] = { 0, 0, 1, -1 }, fy[4] = { 1, -1, 0, 0 };
string s;
void add(int x, int y) {
ver[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
int b(int x, int y) { return (x - 1) * m + y; }
int kmdfs(int x) {
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (vis[y] == nid)
continue;
vis[y] = nid;
if (!match[y] || kmdfs(match[y])) {
match[y] = x;
return 1;
}
}
return 0;
}
void kmdfs2(int x) {
v[x] = 1;
for (int i = head[x]; i; i = nxt[i]) {
int y = ver[i];
if (match[y] && !v[match[y]])
kmdfs2(match[y]);
}
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; i++) {
cin >> s;
for (int j = 0; j < m; j++) {
if (s[j] == '.')
a[i][j + 1] = 1;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!a[i][j])
continue;
for (int k = 0; k < 4; k++) {
int x = i + fx[k], y = j + fy[k];
if (x < 1 || y < 1 || x > n || y > m || !a[x][y])
continue;
add(b(i, j), b(x, y) + n * m);
add(b(x, y) + n * m, b(i, j));
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j]) {
nid++;
if (!kmdfs(b(i, j)))
z[++t] = b(i, j);
}
}
}
if (t) {
for (int i = 1; i <= t; i++) kmdfs2(z[i]);
t = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (v[b(i, j)])
z[++t] = b(i, j);
cout << t << endl;
for (int i = 1; i <= t; i++) {
cout << (z[i] - 1) / m + 1 << " " << (z[i] - 1) % m + 1 << endl;
}
} else
write(0);
return 0;
}
期望问题
\color{#52C41A}\text{A. 数列期望}
题目
随机生成一个
题解
读题后,显然
答案为
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
long long read() {
long long x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
void write(long long x) {
if (x < 0)
putchar('-'), x = -x;
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int N = 30;
const ll mod = 998244353;
ll n, m, a[120000], dp[N][N], ans, jc = 1;
ll qmi(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main() {
n = read();
m = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 1; i <= m; i++) jc = jc * i % mod;
dp[0][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= i; j++) {
for (int k = 0; k < i; k++) dp[i][j] = (dp[i][j] + dp[k][j - 1] * qmi(i, mod - 2)) % mod;
ans = (ans + dp[i][j] * a[j] % mod) % mod;
}
write(ans);
return 0;
}
\color{#52C41A}\text{B. 彩球抽取}
题目
袋子里有
题解
把颜色改成
但是次数
- 取出的第一个球的颜色是
j ,第二个不是,f_{i,j,k}\leftarrow f_{i-1,j,k-1}\times\frac{k}{n}\times\frac{n-k}{n-1} 。 - 取出的第一个球的颜色不是
j ,第二个是,f_{i,j,k}\leftarrow f_{i-1,j,k+1}\times\frac{n-k}{n}\times\frac{k}{n-1} 。 - 取出的两个球的颜色都是或都不是
j ,球的个数不变,f_{i,j,k}\leftarrow f_{i-1,j,k}\times(1-2\times\frac{k}{n}\times\frac{n-k}{n-1}) 。