杂题
2024-11-04
暂无标签

哭いた閃光が目に刺さる / お別かれの鐘が鳴る / 神が成した歴史の / 結ぶ答えは砂の味がする


A. 美味しい美味しいハンバーグ

https://vjudge.net/contest/669299#problem/A

有一个很神奇的随机化做法:我们从前往后串签子,最开始签子数为 \(0\),如果当前任何一个签子都没办法再串这一个就新增签子。如果没有签子可以用了,就说明这块肉不合法,挪到前 \(k\) 块中的随机位置使其优先被选择。

你会发现这个做法跑得很快,spdarkle 说因为 \(K\) 很小还保证有解,所以期望次数是非常对的。我太菜了他也没细说所以开摆。

由于神秘原因,我的 std::mt19937::operator() 如果外边不套一层 abs 就会起飞 晕

#include <bits/stdc++.h>
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n, k;
    std::cin >> n >> k;
    struct _ { int l, r, u, d; };
    std::vector<_> a(n + 1), b(k + 1);
    std::mt19937 rand(time(nullptr));
    auto random = [&](int l, int r) {
        return l + std::abs((int)rand()) % (r - l + 1);
    };
    for (int i = 1; i <= n; ++i)
        std::cin >> a[i].l >> a[i].u >> a[i].r >> a[i].d;
    for (;;) {
        std::copy(a.begin() + 1, a.begin() + k + 1, b.begin() + 1);
        for (int i = k + 1; i <= n; ++i) {
            for (int j = 1; j <= k; ++j) {
                if (std::min(b[j].r, a[i].r) >= std::max(b[j].l, a[i].l) && std::min(b[j].d, a[i].d) >= std::max(b[j].u, a[i].u)) {
                    b[j].l = std::max(b[j].l, a[i].l);
                    b[j].r = std::min(b[j].r, a[i].r);
                    b[j].u = std::max(b[j].u, a[i].u);
                    b[j].d = std::min(b[j].d, a[i].d);
                    goto issol;
                }
            }
            std::swap(a[i], a[random(1, i)]);
            goto nosol;
        issol: ;
        }
        for (int i = 1; i <= k; ++i)
            std::cout << b[i].l << ' ' << b[i].u << '\n';
        break;
    nosol: ;
    }
    return 0;
}

B. Shik and Travel

https://vjudge.net/contest/669299#problem/B

最小化最大,显然需要二分答案,check 打个 DP。具体怎么 DP 呢?首先考虑暴力,设 \(f_{u, a, b}\) 表示在当前 \(mid\) 的限制下,从 \(u\) 出发到第一个叶子距离为 \(a\),最后一个距离为 \(b\) 的可行性。那么有 \(f_{u,a,b} = (f_{l,a,i}\land f_{r,j,b})\lor (f_{r,a,i}\land f_{l,j,b})\)

先不说转移,状态数已经难以接受。所以我们从状态数入手,显然如果存在一个 \(a'\le a\)\(b'\le b\),那么 \((a',b')\) 就是无用状态。也就是说,我们把所有有用的 \((a, b)\)\(a\) 从小到大排序,\(b\) 应该是单调递减的;考虑这个抽象的过程,相当于从头到尾遍历一串状态,在路上碰到的所有较大的 \(b\) 都会被当场丢弃。

感性思考可以发现当我们试图让 \(b\) 最小的时候,这个状态最有用(即最不可能被丢弃)。对于一个 \(f_{l,a,i}\),我们找到能够转移的 \(f_{r,j,b_{\min}}\) 来转移。正确性很好证明,我们的 check 只要求在根节点上存在任意合法状态,那只需要让最不可能被丢弃的不被丢弃即可,更可能被丢弃的状态是否被丢弃就不会产生影响了。

优化后的状态数在 \(u\) 上只会增加 \(\min(|a_l|,|b_r|)+\min(|a_r|,|b_l|)\),即两个儿子上的状态数较小者,参考 DSU on tree,是 \(O(n\log)\) 的。加上对状态排序的数据结构,复杂度 \(O(n\log^2 n\log V)\)

#include <bits/stdc++.h>
const long long inf = 1e18;
int main() {
#ifdef ONLINE_JUDGE
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
#else
    std::freopen(".in", "r", stdin);
    std::freopen(".out", "w", stdout);
#endif
    int n;
    std::cin >> n;
    std::vector<std::vector<std::pair<int, int> > > g(n + 1);
    for (int i = 2, x, w; i <= n; ++i) {
        std::cin >> x >> w;
        g[x].emplace_back(i, w);
    }
    long long res = -1;
    for (long long l = 0, r = inf, mid; l <= r; ) {
        mid = (l + r) >> 1;
        if ([&](void) -> bool {
            std::vector<std::vector<std::pair<long long, long> > > f(n + 1);
            std::function<void(int)> DFS = [&](int x) {
                if (g[x].empty()) {
                    f[x].emplace_back(0, 0);
                    return;
                }
                long long lim = mid;
                for (auto [i, w] : g[x])
                    DFS(i), lim -= w;
                std::vector<std::pair<long long, long long> > t;
                for (int i = 0; i < 2; ++i) {
                    int j = 0;
                    auto l = g[x][0], r = g[x][1];
                    for (auto [a, b] : f[l.first]) {
                        for (; j + 1 < (int)f[r.first].size() && f[r.first][j + 1].first + b <= lim; ++j);
                        if (j < (int)f[r.first].size() && f[r.first][j].first + b <= lim)
                            t.emplace_back(a + l.second, f[r.first][j].second + r.second);
                    }
                    std::swap(g[x][0], g[x][1]);
                }
                std::sort(t.begin(), t.end());
                for (auto [a, b] : t)
                    if (f[x].empty() || f[x].back().second > b)
                        f[x].emplace_back(a, b);
                return;
            };
            DFS(1);
            return !f[1].empty();
        } ())
            res = mid, r = mid - 1;
        else 
            l = mid + 1;
    }
    std::cout << res << '\n';
    return 0;
}

D. Candy Piles

https://vjudge.net/contest/669299#problem/D

不难发现我们可以把题意转化为,把所有列从大到小排列,每次可以选择删掉最左边一列或最下边一行,删空者胜。

把该柱状图转化为网格图,定义边界为胜利点,对于非边界上的点,其一定可以往右走、往上走。若一个点上方、右侧点均为胜利点,此点为失败点;否则,此点为胜利点。


一言 - Hitokoto