「题目描述」

给定一个 $2$ 行 $N$ 列的网格图,每次询问一个区间内所有点的最小生成树,要求支持修改边权。

共 $M$ 次操作,$ 1 \leq N, M \leq 60000 $

「题目链接」

BZOJ 3995 道路修建 「SDOI 2015」

「解题思路」

做过 BZOJ 1018 堵塞的交通 的话,应该能容易想到用线段树维护区间上的信息。

假设某线段树节点维护区间 $[l, r]$ ,区间中点 $m$ ,那么左儿子维护 $[l, m]$ ,右儿子维护 $[m + 1, r]$ 。

考虑一个区间中的点(两排),将四个角上的点称之为顶点,其他的称之为内部的点。

能和外界联通的点只有顶点,所以为了合并后整体联通,必须保证 内部的点和至少一个顶点联通

保证这一点后,对每个区间就只需考虑四个顶点了。

考虑进行信息合并,能把两边连起来的,只有从 $m$ 到 $m + 1$ 的两条边,涉及左儿子的右上、右下,右儿子的左上、左下四个顶点。

那么我们主要考虑的,将是从 $m$ 到 $m + 1$ 的两条边,左儿子的右上、右下,右儿子的左上、左下四个顶点。

这个时候我们就会发现,这两条边是不能随便连的,因为我们不知道这四个顶点间的连通性。

不合适的连边,会导致内部的点不和顶点联通,或者形成环。

这就启示我们,需要把顶点间的连通性加入信息的状态。

每个线段树节点维护 $f(a, b)$ ,表示左上角和左下角连通性为 $a$ ,右上角和右下角连通性为 $b$ 时的最优答案。连通性为 $1$ 代表联通,为 $0$ 代表不联通。

然后合并的时候根据四个端点的联通性,选择连一条边,或者都连上,计算即可。

在这里给出核心代码:

Info merge(const Info &l, const Info &r, int lenU, int lenD){
    // l 代表左儿子信息,r 代表右儿子信息, lenU 代表 m 到 m + 1 的上边那条边,lenD 代表下边那条
    Info i;

    int lenMin = std::min(lenU, lenD), lenSum = lenU + lenD;
    // lenMin 是连一条边的代价,lenSum 是都连上的代价。
    for(int a = 0; a <= 1; a++){
        for(int b = 0; b <= 1; b++){
            i[a][b] = std::min(
                lenMin + l[a][1] + r[1][b], // 各自联通,只能连一条边,否则会形成环
                lenSum + std::min(l[a][0] + r[1][b], l[a][1] + r[0][b]) // 至少一对点不联通,必须连两条边
            );
        }
    }

    return i;
}

总结:主要问题在于信息的状态表示,在转移或者合并不了,或者无法保证合法时,可以考虑更改状态表示。

「AC 代码」

#include <cstdio>
#include <algorithm>

struct Info{
    int f[2][2];

    int* operator[](int i){
        return f[i];
    }

    const int* operator[](int i) const{
        return f[i];
    }

    Info(int a = 0, int b = 0, int c = 0, int d = 0){
        f[0][0] = a, f[0][1] = b, f[1][0] = c, f[1][1] = d;
    }

    static Info merge(const Info &l, const Info &r, int lenU, int lenD){
        Info i;

        int lenMin = std::min(lenU, lenD), lenSum = lenU + lenD;
        for(int a = 0; a <= 1; a++){
            for(int b = 0; b <= 1; b++){
                i[a][b] = std::min(lenMin + l[a][1] + r[1][b], lenSum + std::min(l[a][0] + r[1][b], l[a][1] + r[0][b]));
            }
        }

        return i;
    }
};

const int MAX_N = 60000;

int lenU[MAX_N], lenD[MAX_N], lenV[MAX_N + 1];

inline Info calc(int x){
    return Info(0, 0, 0, lenV[x]);
}

struct Node{
#define mid ((this->l + this->r) >> 1)
    int l, r;
    Node *lc, *rc;
    Info i;

    Node(int l = 0, int r = 0) : l(l), r(r), lc(NULL), rc(NULL) {}

    void update(){
        i = Info::merge(lc->i, rc->i, lenU[mid], lenD[mid]);
    }

    void build(){
        if(r - l == 1){
            i = calc(l);
        } else{
            (lc = new Node(l, mid))->build(), (rc = new Node(mid, r))->build();
            update();
        }
    }

    void refresh(int x){
        if(r - l == 1){
            i = calc(l);
        } else{
            if(x < mid) lc->refresh(x); else rc->refresh(x);
            update();
        }
    }

    Info query(int l, int r){
        if(l == this->l && this->r == r) return i;
        else{
            if(r <= mid) return lc->query(l, r); else if(l >= mid) return rc->query(l, r);
            else return Info::merge(lc->query(l, mid), rc->query(mid, r), lenU[mid], lenD[mid]);
        }
    }
#undef mid
} *gm;

int n, m;

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i < n; i++) scanf("%d", &lenU[i]);
    for(int i = 1; i < n; i++) scanf("%d", &lenD[i]);
    for(int i = 0; i < n; i++) scanf("%d", &lenV[i]);

    (gm = new Node(0, n))->build();

    for(int i = 1; i <= m; i++){
        char cmd;
        do cmd = getchar(); while(cmd != 'C' && cmd != 'Q');

        if(cmd == 'Q'){
            int l, r;
            scanf("%d%d", &l, &r), l--, r--;
            printf("%d\n", gm->query(l, r + 1)[1][1]);
        } else if(cmd == 'C'){
            int x0, y0, x1, y1, val;
            scanf("%d%d%d%d%d", &x0, &y0, &x1, &y1, &val), y0--, y1--;

            if(x0 > x1 || (x0 == x1 && y0 > y1)) std::swap(x0, x1), std::swap(y0, y1);
            
            if(x1 - x0 == 1){
                lenV[y0] = val;
            } else if(y1 - y0 == 1){
                if(x0 == 1) lenU[y0 + 1] = val;
                else if(x0 == 2) lenD[y0 + 1] = val;
            }

            gm->refresh(y0);
        } else{
            throw;
        }
    }

    return 0;
}