CSP-S 2022 题解

来云参赛一下。

T1 假期计划

首先用 BFS 预处理出每对点是否 \(k\) 步可达,然后结合数据范围考虑枚举其中的两个点,不难想到枚举 \(B,C\),只需要预处理出 \(1 \to B\)\(1 \to C\) 路径上权值最大的景点即可。

但是发现这样可能会经过重复的景点,不过这个也很好修,预处理的时候记权值前 \(3\) 大的景点就行了,这样 \(3 \times 3 = 9\) 种组合中一定有一个合法的。时间复杂度 \(O(n^2)\)

想了半天怎么求这个全源最短路,30min 了才反应过来是原来没有边权的,这下小丑了😢。

code
/*
挥拂去蒙尘半生的晦暗
用这嶙峋双臂迎接 振翅吧 我的蝴蝶
最后一支 赤诚赞歌盘旋
潮起潮落翻覆昼夜 澎湃在生命刻度之前
此刻色彩挣脱谎言 献予你 赤红纸花遍野
*/
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long LL;
const int N = 2.5e3 + 5, K = 1e2 + 5;

int n, m, k, dis[N], v[N][4]; bool w[N][N];
int q[N], hd, tl;
LL a[N], ans;
vector <int> e[N];

int main() {
    ios :: sync_with_stdio(0);
    cin >> n >> m >> k;
    for (int i = 2; i <= n; i++) cin >> a[i];
    for (int i = 1, x, y; i <= m; i++) {
        cin >> x >> y;
        e[x].pb(y), e[y].pb(x);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) dis[j] = -1;
        q[hd = tl = 1] = i, dis[i] = 0;
        while (hd <= tl) {
            int u = q[hd++];
            for (int v : e[u]) if (dis[v] == -1) dis[v] = dis[u] + 1, q[++tl] = v;
        }
        for (int j = 1; j <= n; j++) 
            w[i][j] = dis[j] != -1 && dis[j] <= k + 1; 
    }
    for (int i = 2; i <= n; i++)
        for (int j = 2; j <= n; j++)
            if (i ^ j && w[i][j] && w[1][j]) {
                int cur = j;
                for (int k = 1; k <= 3; k++) if (a[cur] > a[v[i][k]]) swap(cur, v[i][k]);
            }
    for (int i = 2; i <= n; i++)
        for (int j = i + 1; j <= n; j++) if (w[i][j])
            for (int p = 1; p <= 3; p++)
                for (int q = 1; q <= 3; q++)
                    if (v[i][p] && v[j][q] && v[i][p] != j && v[j][q] != i && v[i][p] != v[j][q])
                        ans = max(ans, a[i] + a[j] + a[v[i][p]] + a[v[j][q]]);
    cout << ans << endl;
    return 0;
}

T2 策略游戏

没啥好说的,用线段树维护每个序列 非负数 / 负数 的 最大 / 最小 值,然后分类讨论就行了。代码里为了防止出错就全讨论完了。视 \(n,m\) 同阶,则时间复杂度为 \(O((n+q) \log n)\)

code
/*
挥拂去蒙尘半生的晦暗
用这嶙峋双臂迎接 振翅吧 我的蝴蝶
最后一支 赤诚赞歌盘旋
潮起潮落翻覆昼夜 澎湃在生命刻度之前
此刻色彩挣脱谎言 献予你 赤红纸花遍野
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = N << 2, inf = 1e9 + 5;

int n, m, q, a[N], b[N];

#define ls o << 1
#define rs o << 1 | 1
#define mid ((l + r) >> 1)
#define LS ls, l, mid
#define RS rs, mid + 1, r
struct dat {
    int mi, mx;
    dat(int _mi = inf, int _mx = -inf) : mi(_mi), mx(_mx) {}
};
dat operator + (dat x, dat y) { return dat(min(x.mi, y.mi), max(x.mx, y.mx)); }
struct SGT {
    dat tr[M];
    void up(int o) { tr[o] = tr[ls] + tr[rs]; } 
    void mdf(int o, int l, int r, int p, int v) {
        if (l == r) return tr[o] = dat(v, v), void();
        (p <= mid ? mdf(LS, p, v) : mdf(RS, p, v)), up(o);
    }
    dat qry(int o, int l, int r, int L, int R) {
        if (r < L || l > R) return dat();
        if (L <= l && R >= r) return tr[o];
        return qry(LS, L, R) + qry(RS, L, R);
    }
} ta0, ta1, tb0, tb1;

int main() {
    scanf("%d %d %d", &n, &m, &q);
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) {
        if (a[i] >= 0) ta0.mdf(1, 1, n, i, a[i]);
        else ta1.mdf(1, 1, n, i, a[i]);
    }
    for (int i = 1; i <= m; i++) {
        if (b[i] >= 0) tb0.mdf(1, 1, n, i, b[i]);
        else tb1.mdf(1, 1, n, i, b[i]);
    }
    while (q--) {
        int l1, r1, l2, r2;
        scanf("%d %d %d %d", &l1, &r1, &l2, &r2);
        dat a0 = ta0.qry(1, 1, n, l1, r1);
        dat a1 = ta1.qry(1, 1, n, l1, r1);
        dat b0 = tb0.qry(1, 1, n, l2, r2);
        dat b1 = tb1.qry(1, 1, n, l2, r2);
        bool x0 = a0.mi != inf;
        bool x1 = a1.mi != inf;
        bool y0 = b0.mi != inf;
        bool y1 = b1.mi != inf;
        LL ans = 0;
        if (x0 && x1 && y0 && y1) ans = max(1ll * a0.mi * b1.mi, 1ll * a1.mx * b0.mx);
        if (x0 && x1 && !y0 && y1) ans = 1ll * a1.mi * b1.mx;
        if (x0 && x1 && y0 && !y1) ans = 1ll * a0.mx * b0.mi;
        if (!x0 && x1 && y0 && y1) ans = 1ll * a1.mx * b0.mx;
        if (!x0 && x1 && !y0 && y1) ans = 1ll * a1.mi * b1.mx;
        if (!x0 && x1 && y0 && !y1) ans = 1ll * a1.mx * b0.mx;
        if (x0 && !x1 && y0 && y1) ans = 1ll * a0.mi * b1.mi;
        if (x0 && !x1 && !y0 && y1) ans = 1ll * a0.mi * b1.mi;
        if (x0 && !x1 && y0 && !y1) ans = 1ll * a0.mx * b0.mi;
        printf("%lld\n", ans);
    }
    return 0;
}

T3 星战

首先得把题读对,就是判断是不是内向基环树森林,这等价于每个点出度均为 \(1\)。打个暴力就有 \(60\) 分了。

我们维护集合 \(S\) 表示目前存在的所有边的起点构成的可重集,那么条件成立当且仅当 \(S= \{ 1,2,⋯,n \}\),哈希即可。代码里使用的哈希方式是给每个元素一个随机权值 \(w_i\),令 \(S\) 的权值 \(f_S=\sum_{i \in S} w_i\),那么当 \(f_A = f_B\)\(A,B\) 以高概率相等。维护操作是简单的,时间复杂度 \(O(n + q)\)

code
/*
挥拂去蒙尘半生的晦暗
用这嶙峋双臂迎接 振翅吧 我的蝴蝶
最后一支 赤诚赞歌盘旋
潮起潮落翻覆昼夜 澎湃在生命刻度之前
此刻色彩挣脱谎言 献予你 赤红纸花遍野
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5;

int n, m, q, s[N], cur[N], w[N], all;

int main() {
    srand(20070103);
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) w[i] = rand(), all += w[i];
    int ns = 0;
    for (int i = 1, x, y; i <= m; i++) {
        scanf("%d %d", &x, &y);
        cur[y] += w[x], s[y] += w[x], ns += w[x];
    }
    scanf("%d", &q);
    for (int i = 1, op, x, y; i <= q; i++) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d %d", &x, &y);
            ns -= w[x], cur[y] -= w[x];
        } else if (op == 2) {
            scanf("%d", &x);
            ns -= cur[x], cur[x] = 0;
        } else if (op == 3) {
            scanf("%d %d", &x, &y);
            ns += w[x], cur[y] += w[x];
        } else {
            scanf("%d", &x);
            ns += s[x] - cur[x], cur[x] = s[x];
        }
        puts(all == ns ? "YES" : "NO");
    }
    return 0;
}

T4 数据传输

首先考虑链怎么做。\(k=1\) 就是求链和,\(k=2\) 容易发现肯定不会走出链,于是可以直接 DP,设 \(f_{i,0/1}\) 表示上一个位置和 \(i\) 的距离为 \(0/1\),此时的最小代价,转移是简单的。\(k=3\) 时最多只会走出链一步,于是预处理出每个点相邻点权的最小值,然后设 \(f_{i,0/1/2}\) 就行了,容易验证这样肯定是合法的。

然后就可以恭喜你已经获得了 \(76\) 分了。满分做法也很套路,把转移都写成矩阵的形式,然后倍增就行了。时间复杂度 \(O((n+q) \log n)\),常数还挺大。

code
/*
挥拂去蒙尘半生的晦暗
用这嶙峋双臂迎接 振翅吧 我的蝴蝶
最后一支 赤诚赞歌盘旋
潮起潮落翻覆昼夜 澎湃在生命刻度之前
此刻色彩挣脱谎言 献予你 赤红纸花遍野
*/
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair <int, int> pi;
#define pb push_back
#define fi first
#define se second
#define mem(x, v) memset(x, v, sizeof(x))
const int N = 2e5 + 5, K = 18, inf = 0x3f3f3f3f;

int n, q, k, lg, dep[N], v[N], w[N], p[K][N];
vector <int> e[N];

struct mat {
    LL a[3][3];
    mat() { mem(a, inf); }
    mat operator * (const mat &x) const {
        mat y;
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                for (int k = 0; k < 3; k++)
                    y.a[i][j] = min(y.a[i][j], a[i][k] + x.a[k][j]);
        return y;
    }
    mat operator | (const mat &x) const {
        mat y;
        for (int j = 0; j < 3; j++)
            for (int k = 0; k < 3; k++)
                y.a[0][j] = min(y.a[0][j], a[0][k] + x.a[k][j]);
        return y;
    }
} up[K][N], down[K][N], ans;

void dfs(int u, int f) {
    p[0][u] = f, dep[u] = dep[f] + 1;
    for (int v : e[u]) if (v != f) dfs(v, u);
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = lg; ~i; i--) if (dep[p[i][x]] >= dep[y]) x = p[i][x];
    if (x == y) return x; 
    for (int i = lg; ~i; i--) if (p[i][x] != p[i][y]) x = p[i][x], y = p[i][y];
    return p[0][x]; 
}

int main() {
    scanf("%d %d %d", &n, &q, &k), lg = 31 - __builtin_clz(n);
    for (int i = 1; i <= n; i++) cin >> v[i];
    for (int i = 1, x, y; i < n; i++) {
        cin >> x >> y;
        e[x].pb(y), e[y].pb(x);
    }
    for (int i = 1; i <= n; i++) {
        w[i] = inf;
        for (int to : e[i]) w[i] = min(w[i], v[to]);
        if (k == 1) up[0][i].a[0][0] = v[i];
        if (k == 2) {
            up[0][i].a[1][0] = 0;
            up[0][i].a[0][1] = up[0][i].a[1][1] = v[i];
        }
        if (k == 3) {
            up[0][i].a[1][0] = 0;
            up[0][i].a[1][1] = w[i], up[0][i].a[2][1] = 0;
            up[0][i].a[0][2] = up[0][i].a[1][2] = up[0][i].a[2][2] = v[i];
        }
        down[0][i] = up[0][i];
    }
    dfs(1, 0);
    for (int j = 1; j <= lg; j++)
        for (int i = 1; i <= n; i++) {
            p[j][i] = p[j - 1][p[j - 1][i]];
            up[j][i] = up[j - 1][i] * up[j - 1][p[j - 1][i]];
            down[j][i] = down[j - 1][p[j - 1][i]] * down[j - 1][i];
        }
    for (int i = 1, s, t, r; i <= q; i++) {
        scanf("%d %d", &s, &t), r = lca(s, t);
        mem(ans.a, inf);
        ans.a[0][k - 1] = v[s];
        if (s != r) {
            s = p[0][s];
            for (int j = lg; ~j; j--) if (dep[p[j][s]] >= dep[r]) 
                ans = ans | up[j][s], s = p[j][s];
            ans = ans | up[0][r];
        }
        vector <pi> pa;
        for (int j = lg; ~j; j--) if (dep[p[j][t]] >= dep[r]) pa.pb({j, t}), t = p[j][t];
        reverse(pa.begin(), pa.end());
        for (pi z : pa) ans = ans | down[z.fi][z.se];
        printf("%lld\n", ans.a[0][k - 1]);
    }
    return 0;
}

瑞平一下:

难度:\(2 < 1 < 4 < 3\)

个人对这套题的感觉是,除了 T3 感觉有点神秘,其他的题全是套路题,并且后两题都有巨多部分分,于是像我这种无脑选手都能轻松上 \(300\)。果然 CNOI 要变成代码不写挂大赛了吗(大雾)。

不过据群友们所说,其实 T3 也很套路,只不过我作为确定性算法爱好者平时没怎么练过罢了 qaq。

预估了一下,如果自己在考场上期望得分大概是 \(100+100+[60,100]+[76,100]=[336,400]\) 这样子,不过 T3 大概率是做不出的,更可能是 \(336\) 或者 \(360\)。有点菜。


怎么大伙都 AK 了😢,\(360\) 还是不够看啊。

posted @ 2022-10-30 18:29  came11ia  阅读(176)  评论(2编辑  收藏  举报