Loading

分治

分治有普通分治和 CDQ 分治,主要是将问题分成规模相近的两个子问题,再探究子问题之间的关系。分治优化的点是,他将一个问题拆成了总和为 \(n \log n\) 的若干问题,每个问题都比原问题更好解决。序列分治一般探究两个区间之间的关系,而分治不仅可以在普通的维度上分治,还可以对时间分治。整体二分也是一种分治。以上说的算法模板要十分熟悉。

P6406 Norma

分治模板题,很暴力的分讨+拆贡献,没什么好说的。

#define int long long

const int N = 5e5 + 7;
const long long mod = 1e9;

int n, a[N], ans = 0;
long long s1[N][2], s2[N][2], s3[N][2];

void alison(int l, int r) {
    if (l == r) {
        (ans += (a[l] * a[l] % mod)) %= mod;
        return ;
    }
    if (l + 1 == r) {
        (ans += (a[l] * a[l] % mod + a[r] * a[r] % mod + 2ll * a[l] * a[r] % mod) % mod) %= mod;
        return ;
    }
    int mid = l + r >> 1;
    for (int i = mid; i <= r; i ++)
        for (int j = 0; j < 2; j ++)
            s1[i][j] = s2[i][j] = s3[i][j] = 0;
    int mn = 2e18, mx = -2e18, j = mid, k = mid;
    for (int i = mid + 1; i <= r; i ++) {
        mn = min(mn, a[i]), mx = max(mx, a[i]);
        s1[i][0] = (s1[i - 1][0] + mn * (i - mid) % mod) % mod, s1[i][1] = (s1[i - 1][1] + mn) % mod;
        s2[i][0] = (s2[i - 1][0] + mx * (i - mid) % mod) % mod, s2[i][1] = (s2[i - 1][1] + mx) % mod;
        s3[i][0] = (s3[i - 1][0] + mn * mx % mod * (i - mid) % mod) % mod, s3[i][1] = (s3[i - 1][1] + mn * mx % mod) % mod;
    }
    debugArr2(s1, mid + 1, r, 0, 1);
    debugArr2(s2, mid + 1, r, 0, 1);
    debugArr2(s3, mid + 1, r, 0, 1);
    mn = 2e18, mx = -2e18;
    for (int i = mid; i >= l; i --) {
        mn = min(mn, a[i]), mx = max(mx, a[i]);
        while (j < r && a[j + 1] > mn) j ++;
        while (k < r && a[k + 1] < mx) k ++;
        int w1 = min(j, k), w2 = max(j, k);
        if (w1 > mid) (ans += mn * mx % mod * (((mid + 1) - i + 1 + w1 - i + 1) * (w1 - mid) / 2 % mod) % mod) %= mod;
        debug(mn * mx % mod * (((mid + 1) - i + 1 + w1 - i + 1) * (w1 - mid) / 2 % mod) % mod);
        if (j < k) (ans += mx * ((s1[k][0] - s1[j][0] + mod) % mod + (mid - i + 1) * (s1[k][1] - s1[j][1] + mod) % mod) % mod) %= mod;
        if (k < j) (ans += mn * ((s2[j][0] - s2[k][0] + mod) % mod + (mid - i + 1) * (s2[j][1] - s2[k][1] + mod) % mod) % mod) %= mod;
        if (w2 < r) (ans += (s3[r][0] - s3[w2][0] + mod) % mod + (mid - i + 1) * (s3[r][1] - s3[w2][1] + mod) % mod) %= mod;
        debug(i, j, k, w1, w2, ans);
    }
    debug(l, r, ans);
    alison(l, mid), alison(mid + 1, r);
}

void solve() {
    n = read();
    for (int i = 1; i <= n; i ++)
        a[i] = read();
    alison(1, n);
    cout << ans << '\n';
}

QOJ 8527 Power Divisions

首先朴素 DP 十分 naive,后续的分治也比较套路,难点主要在中间的寻找区间,而不是分治,不细讲。

void alison(int l, int r) {
    if (l == r) return f[l] += f[l - 1], void();
    int mid = l + r >> 1;
    alison(l, mid);
    int sum = 0; rx[0] = mid;
    for (int i = mid + 1; i <= r; i ++) {
        (sum += pw[a[i]]) %= mod;
        rx[sum] = i;
    }
    sum = 0; lx[0] = mid + 1;
    for (int i = mid; i >= l; i --) {
        (sum += pw[a[i]]) %= mod;
        lx[sum] = i;
    }
    sum = 0; set <int> bs; int mx = 0;
    for (int i = mid; i >= l; i --) {
        (sum += pw[a[i]]) %= mod; int tmp = a[i];
        while (bs.find(tmp) != bs.end()) bs.erase(tmp), tmp ++;
        bs.insert(tmp); chkMx(mx, tmp); int tar = (pw[mx + 1] - sum + mod) % mod;
        if (rx.count(tar) && rx[tar] != i) f[rx[tar]] += f[i - 1];
    }
    sum = 0; mx = 0; bs.clear();
    for (int i = mid + 1; i <= r; i ++) {
        (sum += pw[a[i]]) %= mod; int tmp = a[i];
        while (bs.find(tmp) != bs.end()) bs.erase(tmp), tmp ++;
        bs.insert(tmp); chkMx(mx, tmp); int tar = (pw[mx + 1] - sum + mod) % mod;
        if (lx.count(tar) && tar != sum && lx[tar] != i) f[i] += f[lx[tar] - 1];
    }
    lx.clear(), rx.clear();
    alison(mid + 1, r);
}

P3157 动态逆序对

CDQ 分治模板。CDQ 是离线消掉了一维,分治的分界点的划分消掉了第二维,最后使用 BIT 对第三位进行计数。

UOJ612 饮食区

观察删除操作,发现完全没用,现在只有加点和查询操作。考虑朴素的做法,我们二分找第 \(k\) 个位置的操作点,因此考虑整体二分,比较板。

CF603E Pastoral Oddities

前面第一个难点就是观察到充要条件,但这和分治没啥关系,不提。这里需要注意到由于偶数连通块的特殊性,答案是单调的,因此可以整体二分。我们对于那些在这次询问区间中一定存在或者不存在的边提前放入并查集中,然后考虑二分值域,从左往右依次加入边并进行合并,然后处理必然存在的边这里有一个分治趣点:由于 \(ql, qr\)\(vl, vr\) 都只和分治层数相关,我们枚举 \(qr - ql + 1\)\(vr - vl + 1\) 的时间复杂度都是 \(\mathcal{O}(n\log n)\) 的。

P3206 [HNOI2010] 城市建设

渐入佳境。

这题的重点是考察对于一个区间 \([l, r]\),那些固定的边和不固定的边,我们拿这些边跑 Kruskal,哪些是必要的,哪些完全不要的,哪些是不确定的。如果不确定的边量级为 \(\mathcal{O}(len)\),那么时间复杂度就是可以接受的。这个问题就与分治无关了,我们考虑类似于寻找必要条件,我们先对静态边跑一边 Kruskal,再对动态边缩点后的图跑 Kruskal,剩下来的那些边容易发现是 \(\mathcal{O}(k)\) 量级的。

QOJ10706 Red-Blue MST

这题是 WQS 二分和整体二分的完美结合,前提是要对 WQS 二分足够熟悉先,我没这个前提,我先撤退了。

P5163 WD 与地图

首先,可以时间倒流,从删边改成进行加边。考虑每一条边出现在强连通分量的时间节点,如果我们能算出这个,我们就可以使用线段树合并来完成接下来的查询,于是问题的关键是如何求出每条边出现在强连通分量中的时间。先考虑朴素的,我们对每条边,二分出现的时间节点,然后把前 \(x\) 条边插入进图中,得到强连通分量的情况,然后查看这条边是否出现在强连通分量中即可。考虑整体二分,假设当前对于 \([ql, qr]\) 这些边,他们变成强连通分量的时间节点在 \([tl, tr]\) 中,朴素的,考虑先提前处理好 \([1, tl - 1]\) 的缩点情况,然后对 \([tl, mid]\) 这些边在缩点后的图上跑一边 Tarjan,对于在强连通内部的边分到 \([tl, mid]\),否则分到 \([mid + 1, tr]\)

P3350 [ZJOI] 旅行者

网格图分治,本质上其实类似于猫树,每次选择一条分割边,将经过这条分割边的可能的最短路全部处理出来,再分治那些不一定经过这条分割边的询问。时间复杂度是 \(\mathcal{O}(S \sqrt{S} \log S)\) 的,证明比较科幻。

P6976 Distance on Triangulation

和上一道题类似,我们每次选择一条多边形的对角线,使得这个图能够被分成均匀的两部分,然后我们只需要关注这条对角线的两个端点到其他询问的距离即可。分治的处理那些最短路和对角线可能不相交的询问。

事实上对于边呈现笛卡尔树的那种图,我们都可以使用三角剖分分治来解决,大概是你把序列当成一个环,在环上做分治即可。

P5044 [IOI2018] meetings

这一题比较特别,是一类笛卡尔树(最值)分治,我们考虑对当前区间的最大值为分界点。考虑暴力 DP,设 \(f_{l, r}\) 表示区间 \([l,r]\) 的最小代价,那么我们考虑决策点在最大值左边还是右边就可以进行转移了。放到分治上来,如果当前我们已经求出所有 \(f_{i, mid}\)\(f_{mid, i}\),那么我们考虑如何合并这两个 DP 值。同暴力转移,我们能求出 \(f_{l, i}\)\(f_{i, r}\),但是时间复杂度是 \(\mathcal{O}(n^2)\) 的。剩下就和分治没啥关系了,我们发现这两个值是单调的,因此二分出分界点之后,我们可以进行一个区间加法,区间赋值等差数列,线段树即可。

posted @ 2025-08-22 19:19  DE_aemmprty  阅读(27)  评论(0)    收藏  举报