Loading

2024牛客多校第6场

6

A Cake (A)

首先假设字符串已经确定,对Oscar而言,由于一份蛋糕可以为空,在两人都尽可能取最大值的情况下,相当于忽略所有空的部分、只根据字符串的某个前缀 \(s'\) 划分蛋糕,按照字符串中0占比最大的前缀平均划分一定是最优的。回到游戏第一步,已知Oscar的目标,Grammy移动时应当让最优 \(s'\) 中0占比尽可能小,而Oscar应使之尽可能大,可用类似树上dp的思路求解。

学习了xht赛时的dfs函数,比我那坨代码好多了······

double dfs(int f, int i, int it, int cnt1, int cnt2, double mx) {
    if(v[i].size() == 1 && v[i][0].t == f) return mx;
    double ans = it;
    for(int j = 0; j < v[i].size(); j++) {
        int t = v[i][j].t, k = v[i][j].k;
        if(t == f) continue;
        int x1 = cnt1 + (1 ^ k), x2 = cnt2 + 1;
        if(it) {
            ans = min(ans, dfs(i, t, it ^ 1, x1, x2, max(mx, x1 * 1.0 / x2)));
        } else {
            ans = max(ans, dfs(i, t, it ^ 1, x1, x2, max(mx, x1 * 1.0 / x2)));
        }
    }
    return max(ans, mx);
}

B.Cake 2 (B)

找规律题,似乎xcpc的签到偏爱找规律()画图观察每块蛋糕的分布位置可得,\(k \leq n/2\) 时,\(ans=n*k+1\),其中特判 \(k*2=n\)\(ans=n\)\(k > n / 2\) 时同理考虑 \(n-k\) 即可。(听说可以用欧拉定理证明,但我太菜了不会证)

D.Puzzle: Wagiri (D)

(说起来Wagiri是什么意思呢,好奇)

由无向边的环结构想到边双连通分量,被保留的轮边必须位于某个分量内部;再用类似缩点的思想,将每个边双连通分量当作一个点考虑,被保留的切边恰好将所有点连成一棵树。先对所有轮边计算边双连通分量、再对所有切边计算生成树,最后并查集检验连通性即可。注意边双的代码比缩点多一个对边的特判。

主要代码如下,将不合法边的类型记作-1,便于输出:

    for(int i = 1; i <= n; i++) {
        if(!dfn[i]) tarjan(i, 0);
    }
    for(int i = 1; i <= n; i++) {
        if(r[col[i]]) f[find(i)] = find(r[col[i]]);
        else r[col[i]] = i;
    }
    int cntm = 0;
    for(int i = 1; i <= m; i++) {
        int x = e[i].f, y = e[i].t;
        if(e[i].id) {
            if(find(x) != find(y)) {
                f[find(x)] = find(y);
                cntm++;
            } else {
                e[i].id = -1;
            }
        } else {
            if(col[x] != col[y]) {
                e[i].id = -1;
            } else cntm++;
        }
    }
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(f[i] == i) cnt++;
    }
    if(cnt > 1) {
        printf("NO");
        return 0;
    }
    printf("YES\n");
    printf("%d\n", cntm);
    for(int i = 1; i <= m; i++) {
        if(e[i].id >= 0) {
            printf("%d %d\n", e[i].f, e[i].t);
        }
    }

F.Challenge NPC 2 (F)

记节点 \(i\) 连边数量为 \(ind[i]\),若 \(ind[i] = n - 1\) 时必然无解,因为无法找到补图中的任何一条边、将它连入哈密顿路径中;其他情况则必然有解。

可将森林中的多棵树合并为一棵树考虑,从树的根节点出发,以各节点深度将树“分层”,显然同层或层级差 \(\geq 2\) 的节点间没有连边;先将所有偶数层连接、再将所有奇数层连接,即可构造出一组合法解。为避免不同树合并时出现 \(ind[i] = n - 1\) 情况,应选取每棵树直径的端点依次连接。

注意到层级数 \(x\leq 3\) 时,先偶后奇的构造方案出现问题,因此特判 \(n\leq 3\) 情况,当 \(n\geq 4\) 且不存在 \(ind[i] = n - 1\) 时,连接所有树直径形成的最终直径必然 \(\geq 4\),以直径一端为根节点开始分层,使 \(x = d\geq 4\),该方案成立。

特判,之后不需要再考虑无解情况:

    if(n == 2) {
        if(m) printf("-1\n");
        else printf("1 2\n");
        continue;
    } else if(n == 3) {
        if(m == 2) printf("-1\n");
        else {
            if(v[1].size() == 0) printf("2 1 3\n");
            else if(v[2].size() == 0) printf("1 2 3\n");
            else printf("1 3 2\n");
        }
        continue;
    }
    bool p = 0;
    for(int i = 1; i <= n; i++) {
        if(ind[i] == n - 1) {
            printf("-1\n");
            p = 1;
            break;
        }
    }
    if(p) continue;

dfs常规计算直径,合并后ans分层(似乎ans改叫bfs更贴切来着)

    r = o1 = o2 = 0;
    for(int i = 1; i <= n; i++) {
        if(!d[i]) {
            o1 = 0;
            dfs(0, i);
            if(!r) r = o1;
            else v[o2].push_back(o1);
            o2 = o1, o1 = 0;
            dfs(0, o2);
            swap(o1, o2);
        }
    }
    ans(0, r, 1);
    for(int i = 2; i <= n; i += 2) {
        if(l[i].size() == 0) break;
        for(int j = 0; j < l[i].size(); j++) {
            printf("%d ", l[i][j]);
        }
    }
    for(int i = 1; i <= n; i += 2) {
        if(l[i].size() == 0) break;
        for(int j = 0; j < l[i].size(); j++) {
            printf("%d ", l[i][j]);
        }
    }

赛时没想到合并直径,导致分层代码巨大一坨,并且 WA 81pts,哭)

I.Intersecting Intervals (I)

队友最后十分钟交了I题并AC,太强了QwQ

数据范围 \(mn\leq 10^6\),不能使用 \(m^2\) 一类的算法。考虑dp,行与行之间的dp转移可利用最大值性质做预处理等,目标是将复杂度优化为 \(O(m)\),使整体复杂度 \(mn\) 符合要求。

\(dp[i][j]\) 表示第 \(i\) 行第 \(j\) 个数必须选择时的最大值,可在转移过程中保证区间相交。假设 \(k\leq j\),有 \(dp[i][j] = max(dp[i - 1][k] + sum[k][j] + pre[k - 1] + suf[j + 1]\),其中 \(pre[x],suf[x]\) 分别代表以 \(x\) 结尾/开头的最大前缀/后缀和,可 \(O(m)\) 预处理;若 \(pre[j] > pre[k - 1] + sum[k][j]\),则必有 \(pre[j] + sum[j + 1][j + x] > pre[k - 1] + sum[k][j + x]\),故 \(max(pre[k - 1] + sum[k][j])\) 可在dp遍历转移的过程中动态维护;\(suf[j + 1]\) 则仅与 \(j\) 的取值有关。\(k > j\) 时同理计算即可。

    for(int i = 1; i <= n; i++) {
        ll sum = 0;
        pre[0] = 0;
        for(int j = 1; j <= m; j++) {
            s[j] = s[j - 1] + a[i][j];
            if(sum < 0) sum = 0;
            sum += a[i][j]; //注意每行必须选至少一个数,因此sum小于0时先清零再累加
            pre[j] = sum;
        }
        sum = 0;
        suf[m + 1] = 0;
        for(int j = m; j; j--) {
            if(sum < 0) sum = 0;
            sum += a[i][j];
            suf[j] = sum;
        }
        ll mx = MIN;
        for(int j = 1; j <= m; j++) {
            mx = max(mx + a[i][j], dp[i - 1][j] + pre[j]);
            dp[i][j] = mx + max(0ll, suf[j + 1]);
        }
        mx = MIN;
        for(int j = m; j; j--) {
            mx = max(mx + a[i][j], dp[i - 1][j] + suf[j]);
            dp[i][j] = max(dp[i][j], mx + max(0ll, pre[j - 1]));
        }
    }
    ll ans = MIN;
    for(int j = 1; j <= m; j++) {
        ans = max(ans, dp[n][j]);
    }
    printf("%lld\n", ans);

lzz做这题的时候我还在被F恶心,他讲的思路没听到一点,赛后好奇他怎么写的,然后发现我看不懂他代码(哭得更大声了)

J题输出规则太阴间了所以没补TAT

posted @ 2024-08-03 13:12  Aderose_yr  阅读(59)  评论(0编辑  收藏  举报