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(n - 1)\) 次操作聚集到任一个角。所以我们选择距 \((a,b)\) 最近的角,讨论 \(n\) 的奇偶,可发现最多还需 \((n - 1)\) 次操作。

#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(m^2)\)

但注意到每次遍历已填完的序列很浪费,所以我们使用链表,一个序列填完后就把他删除,复杂度 \(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\left(a_u - x + \sum_{v\in \operatorname{son}(u)} f(v), 0\right) \]

复杂度 \(O(n\log a)\)

#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,则一种显然的贪心策略是,尽可能每次操作仅减少一段。

设段长为 \(\operatorname{len}\),则最优情况下,每次操作应当选择 \(\operatorname{len}> 1\) 的、最靠左的段删除,并删除其中一个字符,即可保证只会减少一段;若没有 \(\operatorname{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, 出了就没指望有人做出来(笑)

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

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

\[f[i][0] = f[i-1][1]*2 + f[i-1][0] \]

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

\[\begin{aligned} f[i][j] =& \sum_{i-k\le p\le i-1} \sum_{0\le q\le 1}f[p][q] \\ =& pre[i-1][1] - pre[i-k-1][1] + pre[i-1][0] - pre[i-k-1][0] \end{aligned}\]

注意特判处理 \(i-k-1<0\) 的情况。

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

第一维可以滚动优化。这时发现维护前缀和即可, 设 \(f[i][j]\) 的前缀和为 \(s[i][j]\),可进一步优化转移。又发现 +- 等价,代码中 \(f[1]\) 表示的就是填 +- 的个数,所以转移时 \(\times 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] = \sum_{p=i-k}^{i-1} f[p] = pre[i-1] - pre[i-k-1] \]

枚举 \(n\) 个数中的 +- 总个数,用 0 填充剩余部分,则每个方案对于答案的贡献为 \(f[i]\times C(n-i, 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 @ 2024-12-02 12:02  Luckyblock  阅读(70)  评论(0编辑  收藏  举报