MENU

Solution. CF802N&O April Fools' Problem

Description

两个长度为 $n$ 的序列 $a_{1\dots n}$ 和 $b_{1\dots n}$,选择 $i_{1\dots k}$ 和 $j_{1\dots k}$,使得 $i_p\le j_p$,最小化 $\sum (a_{i_p}+b_{j_p})$。

Medium $1\le k\le n\le 2200$ 4s

Hard $1\le k\le n\le 500000$ 10s

Solution

对于这种限制+匹配,考虑网络流,容易想到的是让所有 $a_i$ 对 $b_j,j\ge i$ 连边,但这样边数是 $O(n^2)$(我感觉这样用 Primal Dual 也能过),我们可以通过如下建图将边数优化到 $O(n)$。

cf802n-flow

这里不需要细讲,很好理解,费用流即可求出答案,复杂度为 $O(nmf)=O(n^3)$,但是跑不满,如果用 Primal Dual 的复杂度是 $O(n^2\log n)$,Medium 到这里就做完了。

Hard 一种方法是用线段树模拟费用流,也就是优化费用流中每次找最短路的过程,也可以理解为一种反悔贪心,实现起来有一定困难,但是非常严谨且直观,思路也很连贯。

upd on 2023.1.16:修改了接下来的内容,给出了相对严谨和清晰的解释

另一种方法是,考虑 wqs 二分 + 反悔贪心。

这里 wqs 存在的意义是,让我们方便判断哪些决策需要进行,可以忽略 $k$ 的限制。

费用流模型每次增广的路径长度单调不降,至于为什么,考虑上一次的增广路径 $s\leadsto u\leadsto v\leadsto t$,假如我们想要使用 $v\leadsto u $ 的反向边,注意 $dis'(s,v)+dis'(u,t)\ge dis(s,v)+dis(u,t)$,算入反向边后,有 $dis'(s,t)\ge dis(s,t)$,这样就比较感性的完成了证明。

于是我们知道本题答案关于 $k$ 是下凸的,可以二分一个斜率,或者理解成给每次匹配一个额外的 cost。

接下来只需要贪心的选取能够为答案造成负贡献的匹配即可。

这里需要使用反悔贪心,具体的,从 $1$ 到 $n$ 遍历 $b_i$,对于每个 $b_i$ 选择最小的 $a_j,j\le i$ 匹配或者改良之前的一组匹配,后者即反悔操作。可以使用堆来维护所有的决策。

这样做实际上相当于逐步向网络中加点,不难发现网络中可能出现一条增广路径或者负环,也就对应了上述的两种决策,这样就证明了算法的正确性。

值得注意的是,我之前一直认为反悔决策只有增广路一种,但是这是平时使用 SSP 带来的错觉,负环也是一种反悔决策,尤其在 wqs + 反悔贪心中,因为这种套路基本都使用上述逐步向网络中加点的过程,这会导致曾经选择的增广路可能不优,从而产生负环。

有负环的网络流可以使用消圈算法或上下界网络流解决,应该是后者更加优秀。

Code

Primal Dual

#define int long long
namespace Main{
    const int N = 10005;
    const int inf = 0x3f3f3f3f3f3f3f3f;
    int tot, cnt, val[N<<1], dis[N], h[N], pnd[N], pid[N];
    bool vis[N];
    struct edge{
        int to, c, id;
        edge() {}
        edge(int to, int c, int id): to(to), c(c), id(id) {}
    };
    vector<edge> g[N];
    struct node{
        int u, dis;
        node() {}
        node(int u, int dis): u(u), dis(dis) {}
        bool operator < (const node &b) const {
            return dis > b.dis;
        }
    };
    void add(int u, int v, int w, int c){
        cnt++;
        g[u].emplace_back(v, c, cnt << 1);
        g[v].emplace_back(u, -c, cnt << 1 | 1);
        val[cnt<<1] = w;
        val[cnt<<1|1] = 0;
    }
    void spfa(int s){
        queue<int> q;
        memset(h, 0x3f, sizeof(h));
        memset(vis, 0, sizeof(vis));
        h[s] = 0, vis[s] = true;
        q.push(s);
        while(!q.empty()){
            int u = q.front(); q.pop();
            vis[u] = false;
            for(edge v: g[u]){
                if(val[v.id] && h[u] + v.c < h[v.to]){
                    h[v.to] = h[u] + v.c;
                    if(!vis[v.to]){
                        vis[v.to] = true;
                        q.push(v.to);
                    }
                }
            }
        }
    }
    bool dijkstra(int s, int t){
        priority_queue<node> q;
        memset(dis, 0x3f, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        dis[s] = 0;
        q.emplace(s, 0);
        while(!q.empty()){
            int u = q.top().u; q.pop();
            if(vis[u]) continue;
            vis[u] = true;
            for(edge v: g[u]){
                int w = h[u] - h[v.to] + v.c;
                if(val[v.id] && dis[u] + w < dis[v.to]){
                    dis[v.to] = dis[u] + w;
                    pnd[v.to] = u, pid[v.to] = v.id;
                    q.emplace(v.to, dis[v.to]);
                }
            }
        }
        return dis[t] != inf;
    }
    pair<int, int> mcmf(int s, int t){
        int minc = 0, maxf = 0;
        spfa(s);
        while(dijkstra(s, t)){
            int flow = inf;
            for(int i = 1; i <= tot; i++) h[i] += dis[i];
            for(int i = t; i != s; i = pnd[i]) flow = min(flow, val[pid[i]]);
            for(int i = t; i != s; i = pnd[i]) val[pid[i]] -= flow, val[pid[i]^1] += flow;
            maxf += flow;
            minc += flow * h[t];
        }
        return {minc, maxf};
    }
    int n, k, a[N], b[N];
    void Main(){
        input(n, k);
        for(int i = 0; i < n; i++) input(a[i]);
        for(int i = 0; i < n; i++) input(b[i]);
        for(int i = 0; i < n; i++) add(2 * n + 1, i + 1, 1, 0), add(i + 1, n + i + 1, 1, a[i] + b[i]), add(n + i + 1, 2 * n + 2, 1, 0);
        for(int i = 1; i < n; i++) add(n + i, n + i + 1, inf, b[i] - b[i-1]);
        add(2 * n + 3, 2 * n + 1, k, 0);
        tot = 2 * n + 3;
        pair<int, int> ans = mcmf(2 * n + 3, 2 * n + 2);
        assert(ans.second == k);
        write(ans.first);
        return;
    }
} // namespace
#undef int

wqs

#define int long long
namespace Main{
    const int N = 500005;
    int n, k, a[N], b[N];
    void Main(){
        input(n, k);
        for(int i = 0; i < n; i++) input(a[i]);
        for(int i = 0; i < n; i++) input(b[i]);
        int l = 0, r = 2e9;
        while(l <= r){
            int mid = l + r >> 1;
            int cnt = 0, val = 0;
            priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
            for(int i = 0; i < n; i++){
                q.emplace(a[i], 0);
                int tmp = q.top().first + b[i] - mid;
                if(tmp < 0){
                    val += tmp;
                    cnt += q.top().second == 0;
                    q.pop();
                    q.emplace(mid - b[i], 1);
                }
            }
            if(cnt == k){
                write(val + k * mid);
                return;
            }
            if(cnt > k) r = mid - 1;
            else l = mid + 1;
        }
        return;
    }
} // namespace
Last Modified: January 16, 2023
Archives Tip
QR Code for this page
Tipping QR Code