C.DFS Game
- 题意:个点的树上,每个点都有一枚硬币。两人博弈,先手在一号点上。每次:当前点有硬币取走、换人。否则,如果存在有硬币的儿子,其中选择一个走一步;否则回到父亲。
双方都希望自己得到的硬币数最多,问先手最后的硬币数。 - 思路:
感觉这个问题还挺有思维难度的,看这篇题解,很详细了,我也只会复述它的话。
首先最基本的结论就是:进入sz为奇数儿子回来会交换先后手,。
然后这又是个零和博弈,你作为先手(有主动权)一定要让自己尽量走:使先手比后手赢得尽量多的儿子(子问题)
结合上面两点分类讨论…… - code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int fa[N], nxt[N], to[N], head[N], ecnt, sz[N];
void add_edge(int u, int v) {nxt[++ecnt] = head[u]; to[ecnt] = v; head[u] = ecnt;}
int dfs(int u) {
sz[u] = 1;
vector<int> V;
int god = -1, bad = 0;
for(int i = head[u]; i; i = nxt[i]) {
int v = to[i], d = dfs(v);
bool f = (sz[v] & 1);
if(!f) {
if(d > 0) {god += d;}
else bad += d;
}
else {V.push_back(d);}
sz[u] += sz[v];
}
sort(V.begin(), V.end());
int sz = V.size();
for(int i = sz; i; i--) {
int x = V[i - 1];
god += (((sz - i) & 1) ? -1 : 1) * x;
}
if(sz & 1) god -= bad;
else god += bad;
return god;
}
int main() {
int n;
scanf("%d", &n);
for(int i = 2; i <= n; i++) {scanf("%d", &fa[i]); add_edge(fa[i], i);}
printf("%d", (n - dfs(1)) >> 1);
return 0;
}
D.Skate
-
题意:从一个点朝四个方向出发,遇到第一个方块或者墙就停下来。问最少加多少个方块,使得从任意位置出发可以到达所有位置。
-
思路:是我想不到的喵喵子思路~
如果出发在外圈(靠墙),可以经过第一/最后一列/行。
如果是方块,那么你可以在这个位置刹车,即四个方向都可选,那么第行能经过,就可以经过列,反之也成立(是双向的)。
用并查集维护行和列的互通情况。
需要加的块个数为min(行连通块数,列连通块数)-1。 -
code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2005;
char s[N][N];
int fa[N];
int g_fa(int u) {return fa[u] == u ? u : fa[u] = g_fa(fa[u]);}
void Merge(int u, int v) {fa[g_fa(u)] = g_fa(v);}
bool mark[N];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n + m; i++) fa[i] = i;
for(int i = 1; i <= n; i++) {scanf("%s", s[i] + 1);}
Merge(1, n), Merge(1, n + 1), Merge(1, n + m);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if(s[i][j] == '#') {Merge(i, j + n);}
}
}
int cnt = 0, ans = 0;
for(int i = 1; i <= n; i++) if(!mark[g_fa(i)]) {cnt++; mark[g_fa(i)] = 1;}
ans = cnt; cnt = 0;
for(int i = 1; i <= n; i++) mark[g_fa(i)] = 0;
for(int i = 1; i <= m; i++) if(!mark[g_fa(i + n)]) {cnt++; mark[g_fa(i + n)] = 1;}
ans = min(ans, cnt);
printf("%d", ans - 1);
return 0;
}
E.Cigar Box
跟昨天考试的某道题有点像。
- 题意:有一个初始排列,。每次可以从中选择一个放在两侧,给最终序列,问用步把变成的操作序列的方案数。
- 思路:每个数最终所在位置由最后一次操作确定(或者它一直静止,保持在初始递增序列中)。而之前把它放在哪里都不重要,也不会对其它数产生影响。
我们另每个值最后一次操作叫关键操作。在最终序列中:第一次前置的是位置的数,后置是位置的数。那么中间的的数一直静止,因为它们不可能在,操作后操作(到两侧就再也进不来了),而如果在,之前操作,即可取代第一次前置(后缀)的地位,矛盾。
因此顺序就是和同时进行。
可以考虑dp,设:做了次操作左边还剩个未归位,右边剩个。
发现其实和可以并为一维,表示未归位的总个数,最后统计贡献的时候再用组合数考虑和交错操作顺序。
还有一个问题:你需要贡献答案时,需要中间的是单增序列,此时已知的是还操作开始时两边未归位个数,这样发现我们需要从结果到开头反着推(就像期望dp一样)。
初始:
- code:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3005;
const int mod = 998244353;
ll dp[N][N], C[N][N];
int a[N];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {scanf("%d", &a[i]);}
dp[m][0] = 1;
for(int i = m - 1; ~i; i--) {
for(int j = 0; j <= n; j++) {
dp[i][j] = 2ll * dp[i + 1][j] * j % mod;
if(j) dp[i][j] = (dp[i + 1][j - 1] + dp[i][j]) % mod;
}
}
for(int i = 0; i <= n; i++) {
C[i][0] = 1;
for(int j = 1; j <= i; j++) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
ll ans = 0;
for(int i = 0; i <= n; i++) ans = (ans + dp[0][n] * C[n][i]) % mod;
for(int i = 1; i <= n; i++) {
for(int j = i; j <= n; j++) {
if(j > i && a[j] < a[j - 1]) break;
int len = j - i + 1;
ans = (ans + dp[0][n - len] * C[n - len][i - 1]) % mod;
}
}
printf("%lld", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人