CSP-S模拟赛6
T1.玩水
一道非常简单的结论题(但是赛时最后半个小时才想出来)。首先考虑两个人的情况,显然只要有一个岔路就可以了。如图:
像这样的c点,我们把它称为一个岔路点。
以此类推,三个人只需要两个岔路点就行。但是分为两种情况:
1.在一个岔路点的严格左上角方向有一个岔路点(因为在经过第一个岔路点之后可以走相同的路来到达第二个岔路点);
2.在一个岔路点上方或左方相邻的地方有一个岔路点(一步到位)。(没有判这个但只挂了20分)
如图所示:
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; const int Z = 1100;
int n, m, ans;
char a[Z][Z];
int dp[Z][Z], sum[Z][Z];
sandom main()
{
fre(water, water);
int T; cin >> T;
while (T--)
{
cin >> n >> m; ans = 0;
for (re i = 1; i <= n; i++) scanf("%s", a[i] + 1);
for (re i = 2; i <= n; i++)//前i行前j列是否有岔路
for (re j = 2; j <= m; j++)
{
dp[i][j] = a[i - 1][j] == a[i][j - 1];
sum[i][j] = dp[i][j] | sum[i - 1][j] | sum[i][j - 1];
}
for (re i = 2; i <= n; i++)//1.在(i, j)的左上方有岔路
for (re j = 2; j <= m; j++)//2.在与(i, j)相邻的位置有岔路
if (dp[i][j] && (dp[i - 1][j] | dp[i][j - 1] | sum[i - 1][j - 1]))//有两个岔路就行
{ ans = 1; break; }
ans ? puts("1") : puts("0");
}
return 0;
}
T2.AVL树
树上贪心。因为把根删除,孩子也全没了,所以中序遍历贪心等价于先序遍历贪心。
我们想要尽可能的保留左孩子,而舍弃右孩子,我们可以优先递归左孩子。对于每一个节点,我们需要判断它是否可以保留,回溯它的所有父亲,对于该节点是左孩子的情况,我们通过深度与AVL的定义,计算出保留它,至少需要右子树中有几个点,直到根,我们便算出了保留这个点需要整棵树至少多大。如果当前剩下的k足够,那就可以选。
首先可以预处理出来,表示深度为i的AVL树,节点数至少多大。有递推式,可以看作一边深度为,一边深度为,在上面接上了根节点。
但是纯贪心会有错误,我们可能在左孩子选了过多的点,而导致右孩子不够选。所以需要预估AVL的深度。我们需要定义:以rt为根的子树的极限可能深度;:已选子树中的最大深度;:如果留下rt,需要至少往下延伸到第几层。在查询时,利用这些数组找到可能的最大深度,进而判定是否留下;当确定一个点被选时,向上更新它的父亲的最大深度信息。
由于这棵树本身就是一棵AVL,所以深度为,时间复杂度为。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 5e5 + 100;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, k;
int ans[Z], f[50], root;
#define lk kid[rt][0]
#define rk kid[rt][1]
int kid[Z][2], dad[Z], dep[Z];
int nw[Z], mx[Z], dis[Z];
inline void update(int u)
{
nw[u] = max(nw[u], dep[u]);
int now = u, rt = dad[u];
while (rt)
{
nw[rt] = max(nw[rt], dep[u]);//已选子树中的最大深度
if (now == lk && rk) mx[rk] = max(mx[rk], nw[rt] - 1);//至少需要多少深度
now = rt, rt = dad[rt];
}
}
inline int query(int u)
{
int now = u, rt = dad[u], sum = 0;
while (rt)
{
if (now == lk) sum += f[max(max(nw[rt], dep[u]) - 1, mx[rk]) - dep[rt]];//把至少需要的深度取出来
now = rt, rt = dad[rt];
}
return sum;
}
void dfs1(int rt)
{
dis[rt] = dep[rt] = dep[dad[rt]] + 1;
if (lk) dfs1(lk), dis[rt] = max(dis[rt], dis[lk]);//下限
if (rk) dfs1(rk), dis[rt] = max(dis[rt], dis[rk]);
}
void dfs2(int rt)
{
if (!rt) return;
if (query(rt) <= k - 1)
{
ans[rt] = 1, k--;
update(rt);
}
if (lk && dis[lk] >= mx[rt])//优先左子树
{
mx[lk] = max(mx[lk], mx[rt]);
if (rk) mx[rk] = max(mx[rk], mx[rt] - 1);
}
else if (rk)//左子树不够就右子树
{
mx[rk] = max(mx[rk], mx[rt]);
if (lk) mx[lk] = max(mx[lk], mx[rt] - 1);
}
dfs2(lk), dfs2(rk);
}
sandom main()
{
fre(avl, avl);
n = read(), k = read();
f[1] = 1;
for (re i = 2; i <= 30; i++) f[i] = f[i - 1] + f[i - 2] + 1;
for (re i = 1; i <= n; i++)
{
dad[i] = read();
if (dad[i] == -1) { dad[i] = 0, root = i; continue; }
if (i < dad[i]) kid[dad[i]][0] = i;
else kid[dad[i]][1] = i;
}
dfs1(root); dfs2(root);
for (re i = 1; i <= n; i++) putchar(ans[i] | 48); putchar('\n');
return 0;
}
T3.暴雨
先抛出一个问题,对于题面:如果一个物体,既不属于一个空间的内部,也不处于这个空间的边界,那么它处于这个空间的什么位置。(一个普通的物理积水能被出题人说的这么高深)。
定义:前i块土地,最大高度为j,且之后存在一个比j高的土地(可以理解为之后有一个无限高的墙),铲平了k块,积水体积为偶数/奇数的方案数。这是前缀dp,同理,后缀dp为后i块土地。
虽然写出了dp式子,但我们发现即使离散化,复杂度也是,发现k的范围很小,所以从这里入手。对于一个区间,初始最大高度只有一个而且是固定的,每铲平一块,最大高度值最多会变化一次,而且是连续的。所以一个区间的最大高度取值,只有k+1种,我们通过set或者map预处理出来每一段前k+1大的土地以及排名,转移时只用到这些。这样时间复杂度就是
最后合并统计答案,枚举最大值的位置,强制左侧都小于它,右侧小于等于它,避免了重复与非法。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 25010; const int M = 30; const int mod = 1e9 + 7; typedef long long ll;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
ll n, m, ans;
int h[Z], pre[Z][M], suf[Z][M];
int f[Z][M][M][2], g[Z][M][M][2];
struct cmp { bool operator ()(int A, int B) { return A > B; } };
set <int, cmp> fr, nx;
void DP(int i, int p, int dp[Z][M][M][2], int tot[Z][M])
{
int t, tmp, o = 1;
for (re j = 1; j <= tot[i][0]; j++) if (tot[i][j] == h[i]) t = j;
for (re j = 1; j <= tot[i + p][0]; j++) if(tot[i + p][j] == h[i]) o = 0;
for (re j = 1; j <= tot[i + p][0]; j++)
{
tmp = tot[i + p][j];
for (re k = 0; k <= m; k++)
{
if (h[i] > tmp)
{
(dp[i][t][k][0] += dp[i + p][j][k][0]) %= mod;
(dp[i][t][k][1] += dp[i + p][j][k][1]) %= mod;
(dp[i][j + o][k + 1][(tmp + 0) % 2] += dp[i + p][j][k][0]) %= mod;
(dp[i][j + o][k + 1][(tmp + 1) % 2] += dp[i + p][j][k][1]) %= mod;
}
else
{
(dp[i][j][k][(tmp - h[i] + 0) % 2] += dp[i + p][j][k][0]) %= mod;
(dp[i][j][k][(tmp - h[i] + 1) % 2] += dp[i + p][j][k][1]) %= mod;
(dp[i][j][k + 1][(tmp + 0) % 2] += dp[i + p][j][k][0]) %= mod;
(dp[i][j][k + 1][(tmp + 1) % 2] += dp[i + p][j][k][1]) %= mod;
}
}
}
}
void init(set <int, cmp> &s, int i, int tot[Z][M])
{
s.insert(h[i]);
for (auto it = s.begin(); it != s.end(); ++it)
{
tot[i][++tot[i][0]] = *it;
if (tot[i][0] > m ) break;
}
}
sandom main()
{
fre(rain, rain);
n = read(), m = read();
for (re i = 1; i <= n; i++) h[i] = read();
fr.insert(0), nx.insert(0);
pre[0][++pre[0][0]] = 0, suf[n + 1][++suf[n + 1][0]] = 0;
for (re i = 1; i <= n; i++) init(fr, i, pre);
for (re i = n; i >= 1; i--) init(nx, i, suf);
f[0][1][0][0] = g[n + 1][1][0][0] = 1;
for (re i = 1; i <= n; i++) DP(i, -1, f, pre);
for (re i = n; i >= 1; i--) DP(i, +1, g, suf);
for (re i = 1; i <= n; i++)
for (re k = 0; k <= m; k++)
{
ll tmp1 = 0, tmp2 = 0;
for (re j = 1; j <= pre[i - 1][0]; j++) if (pre[i - 1][j] < h[i]) (tmp1 += f[i - 1][j][k][0]) %= mod;
for (re j = 1; j <= suf[i + 1][0]; j++) if (suf[i + 1][j] <= h[i]) (tmp2 += g[i + 1][j][m - k][0]) %= mod;
ans += tmp1 * tmp2 % mod;
tmp1 = tmp2 = 0;
for (re j = 1; j <= pre[i - 1][0]; j++) if (pre[i - 1][j] < h[i]) (tmp1 += f[i - 1][j][k][1]) %= mod;
for (re j = 1; j <= suf[i + 1][0]; j++) if (suf[i + 1][j] <= h[i]) (tmp2 += g[i + 1][j][m - k][1]) %= mod;
ans += tmp1 * tmp2 % mod;
ans %= mod;
}
cout << ans << endl;
return 0;
}
T4.置换
我只会暴力
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】