CSUACM2024新生赛 - 第3场 题解

写在前面

比赛地址:https://www.luogu.com.cn/contest/217790

A 袋鼠的迷宫

对于所有相邻的两列,如果两行都存在障碍物,则无法继续前进,此时判断为无法到达即可。

复制复制
#include <iostream>
#include <string>
using namespace std;
const int N = 1e6 + 5;
const string ans[2] = {"NO", "YES"};
int n;
bool vis[2][N];
int main() {
cin >> n;
for(bool j : {0, 1})
for (int i = 1; i <= n; i++) {
char c;
cin >> c;
vis[j][i] = (c == '.');
}
bool flag = 1;
for (int i = 2; i <= n; i++)
flag &= ((vis[0][i - 1] && vis[0][i]) || (vis[1][i - 1] && vis[1][i]));
cout << ans[flag] << endl;
return 0;
}

B 袋鼠大元帅

首先发现我们可以把所有袋鼠用 2(n1) 次操作聚集到任一个角。所以我们选择距 (a,b) 最近的角,讨论 n 的奇偶,可发现最多还需 (n1) 次操作。

#include <iostream>
using namespace std;
int main() {
int n, a, b;
cin >> n >> a >> b;
if(a*2 <= n) {
for (int i = 1; i < n; i++)
cout << 'U';
for (int i = 1; i < a; i++)
cout << 'D';
}
else {
for (int i = 1; i < n; i++)
cout << 'D';
for (int i = n; i > a; i--)
cout << 'U';
}
if(b*2 <= n) {
for (int i = 1; i < n; i++)
cout << 'L';
for (int i = 1; i < b; i++)
cout << 'R';
}
else {
for (int i = 1; i < n; i++)
cout << 'R';
for (int i = n; i > b; i--)
cout << 'L';
}
return 0;
}

C 袋鼠排队

首先考虑暴力模拟,依次向每个序列末尾添加,如果遇到 1 就表示当前这个序列已经填完,之后再填到这个序列就跳过。

考虑复杂度,若某个序列特别长,在其他序列都填完后每次再填入一个数需要遍历每个序列,复杂度 O(m2)

但注意到每次遍历已填完的序列很浪费,所以我们使用链表,一个序列填完后就把他删除,复杂度 O(m)

需使用vector存储每个序列,防止mle。

#include <iostream>
#include <vector>
using namespace std;
const int N = 3e5 + 5;
int n, m, b[N], nxt[N], lst[N];
vector<int> a[N];
int main() {
ios::sync_with_stdio(0);
cin.tie(nullptr);
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> b[i];
n += (b[i] == -1);
}
for (int i = 1; i <= n; i++)
nxt[i] = i % n + 1, lst[i] = i - 1;
lst[1] = n;
for (int i = 1, j = 1; i <= m; i++, j = nxt[j]) {
if(b[i] != -1)
a[j].push_back(b[i]);
else
lst[nxt[j]] = lst[j], nxt[lst[j]] = nxt[j];
}
cout << n << endl;
for (int i = 1; i <= n; i++) {
cout << a[i].size() << ' ';
for(int j = 0; j < a[i].size(); j++)
cout << a[i][j] << ' ';
cout << endl;
}
return 0;
}

D 袋鼠总统

二分答案加树形 dp。

答案显然具有单调性(若某个 x 能满足预算,则比他大的 x 都能满足预算),考虑二分答案。

考虑如何检查某个 x 是否合法。设 f(u) 表示若要满足 u 子树内的预算至少还需要从祖先那里获得多少补助,所以转移时有:

f(u)=max(aux+vson(u)f(v),0)

复杂度 O(nloga)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5, A = 1e9;
int n, a[N], head[N], nxt[N], to[N], edge_cnt;
inline void add_edge(int u, int v) {
nxt[++edge_cnt] = head[u];
head[u] = edge_cnt;
to[edge_cnt] = v;
}
ll dfs(int u, int x) {
ll res = a[u] - x;
for (int i = head[u]; i; i = nxt[i])
res += dfs(to[i], x);
return max(res, 0ll);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 2; i <= n; i++) {
int p;
cin >> p;
add_edge(p, i);
}
int l = 0, r = A;
while(l < r) {
int mid = (l + r) / 2;
if(dfs(1, mid))
l = mid + 1;
else
r = mid;
}
cout << l << endl;
return 0;
}

E 袋鼠喝奶茶

贪心,细节题

因为每次都会删去整段前缀,所以可以把连续的 01 子串看为一段。显然一次操作减少的段数只能为 1 或 2,则一种显然的贪心策略是,尽可能每次操作仅减少一段。

设段长为 len,则最优情况下,每次操作应当选择 len>1 的、最靠左的段删除,并删除其中一个字符,即可保证只会减少一段;若没有 len>1 的段,则每次删除就会减少两端,但是由于只进行一步操作也算一次,所以要记得上取整。

思路比较好想,细节可以看代码。

#include <iostream>
#include <vector>
using namespace std;
void solve() {
int n; cin >> n;
string s; cin >> s;
s = '1' + s;
vector<int> cnt(n + 1, 0); //统计每一段的长度
int nw = 0; //统计总段数
for (int i = 1; i <= n; ) {
char ch = s[i];
int j = i + 1;
while(j <= n && s[j] == ch) j++;
cnt[++nw] = j - i;
i = j;
}
int j = 1, x = -1; //删除操作具有单调性,用j表示删到哪里,x判断有多少不可删
for (int i = 1; i <= nw; i++) {
if (j < i) j = i; //只能删除当前段或者后面的
while (j <= nw && cnt[j] <= 1) j++;
if (j >= nw + 1) {x = i - 1; break;} //没有可以删的就break直接计算
cnt[j]--;
}
if (x != -1) cout << x + (nw - x + 1) / 2 << endl;
else cout << nw << endl; //最多也只能删分段的次数
}
int main() {
int t;
cin >> t;
while (t--)
solve();
return 0;
}

F 袋鼠逛商场1

gxd出的,灵感来源是ICPC Taichung Regional 的 H, 出了就没指望有人做出来(笑)

若不限制初始位于第一层,则本题为:限制条件为连续的 +- 不能超过 k1 个,求构造长为 n 的序列的方案数。显然此时 +- 是等价的,则考虑用 0 表示操作 0,1 表示操作 +-。考虑 DP,设 f[i][j] 表示,只考虑前 i 个位置,第 i 个位置放操作 j 时的合法方案数,则:

  • 0 没有限制可以直接转移:

f[i][0]=f[i1][1]2+f[i1][0]

  • 为了使当前位置合法,连续 1 不超过 k1 个,可以枚举非 1 的断点 p 求和,并通过前缀和 pre 实现 O(1) 转移:

f[i][j]=ikpi10q1f[p][q]=pre[i1][1]pre[ik1][1]+pre[i1][0]pre[ik1][0]

注意特判处理 ik1<0 的情况。

最后处理从第一层开始的限制,把序列翻转过来考虑,题意变为从任意层开始,最后必须落在第一层,因为第一层不能下行所以答案为 f[n][1]+f[n][0]

第一维可以滚动优化。这时发现维护前缀和即可, 设 f[i][j] 的前缀和为 s[i][j],可进一步优化转移。又发现 +- 等价,代码中 f[1] 表示的就是填 +- 的个数,所以转移时 ×2

#include <iostream>
using namespace std;
const int N = 1e6 + 5, mod = 998244353;
int n, k, s[N];
inline int add(int x, int y) {
int res = x + y;
return res >= mod ? res - mod : res;
}
inline int sub(int x, int y) {
int res = x - y;
return res < 0 ? res + mod : res;
}
int main() {
cin >> n >> k;
int f[2] = {1, 0};
s[0] = 1;
for (int i = 1; i <= n; i++) {
f[0] = add(f[0], 2*f[1]%mod);
f[1] = sub(s[i - 1], i - k < 0 ? 0 : s[i - k]);
s[i] = add(s[i - 1], add(f[0], f[1]));
}
cout << add(f[0], f[1]) << endl;
return 0;
}

G 袋鼠逛商场2

DP, 乘法逆元

请先阅读前一题题解。

和前一题的区别在于0失去了断点功能,而+-互为断点

dp转移式变为:

f[i]=p=iki1f[p]=pre[i1]pre[ik1]

枚举 n 个数中的 +- 总个数,用 0 填充剩余部分,则每个方案对于答案的贡献为 f[i]×C(ni,n)

特殊限制等处理方式同前。

#include <iostream>
using namespace std;
const int N = 1e6 + 5, mod = 998244353;
int n, k, s[N];
inline int add(int x, int y) {
int res = x + y;
return res >= mod ? res - mod : res;
}
inline int sub(int x, int y) {
int res = x - y;
return res < 0 ? res + mod : res;
}
inline int mul(int x, int y) {
return 1ll * x * y % mod;
}
int qpow(int x, int y) {
int res = 1;
while(y) {
if(y&1)
res = mul(res, x);
x = mul(x, x);
y >>= 1;
}
return res;
}
int main() {
cin >> n >> k;
s[0] = 1;
int res = 1;
for (int i = 1, c = 1; i <= n; i++) {
int f = sub(s[i - 1], i - k < 0 ? 0 : s[i - k]);
c = mul(c, mul(qpow(i, mod - 2), n + 1 - i));
res = add(res, mul(f, c));
s[i] = add(s[i - 1], f);
}
cout << res << endl;
return 0;
}

写在最后

如果澳大利亚的袋鼠集结入侵乌拉圭,那么最后获得胜利的是哪一方? - 知乎:
https://www.zhihu.com/question/320455456

posted @   Luckyblock  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示