BZOJ4170 极光(CDQ分治 或 树套树)

本文介绍了一种使用CDQ分治及树套树方法解决BZOJ上特定问题的策略。该问题要求计算曼哈顿距离内点的数量,通过坐标旋转转化为二维前缀和问题。提供了两种实现方案的源代码,一种利用CDQ分治优化查询,另一种采用树状数组嵌套树状数组的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门
BZOJ上的题目没有题面……
题面
【样例输入】
3 5
2 4 3
Query 2 2
Modify 1 3
Query 2 2
Modify 1 2
Query 1 1
【样例输出】
2
3
3

这道题稍微分析一下就知道是求一个一个点曼哈顿距离小于k的的范围内的点的个数(把下标看做x,把值看做y)。然后我们只需要旋转一下坐标轴就变成了和“Mokia”或“简单题”一样的CDQ分治裸题了,求二维空间前缀和。
首先将询问按x排序,然后开始分治过程,计算左半区间对右半区间的贡献,然后就写完了呀!

然后是树套树做法,这个就更Naive了呀,直接套,反正就是求区间前缀和,乱搞搞就A了(不知道为什么对内存的需求不对啊……)。

CDQ版:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <assert.h>
#define LL int
#define MAXM 300005
#define MAXN 100005
using namespace std;
int a[MAXN];
LL ans[MAXN];
inline int Min(int a, int b) { return a < b ? a : b; }
struct Querys{
    int t, x, y, v, k; Querys(){}
    Querys(int a, int b, int c, int d, int e):t(a),x(b),y(c),v(d),k(e){}
    inline bool operator < (const Querys &r) const {
        if(x == r.x && y == r.y) return t < r.t;
        else if(x == r.x) return y < r.y;
        else return x < r.x;
    }
} q[MAXM], nq[MAXM];
int n, m, cnt = 0, qn, N;
inline void GET(int &n) {
    char c; n = 0;
    do c=getchar(); while('0' > c || c > '9');
    do n=n*10+c-'0',c=getchar(); while('0' <= c && c <= '9');
}
namespace BIT {
    LL t[MAXM];
    inline void Add(int x, int v) {
        for(; x <= N; x += x&-x) t[x] += v;
    }
    inline LL Q(int x, LL sum = 0) {
        for(; x; x -= x&-x) sum += t[x];
        return sum;
    }
}
void cdq(int L, int R) {
    if(L >= R) return;
    using namespace BIT;
    int mid = (L + R) >> 1, lp = L, rp = mid+1;
    for(int i = L; i <= R; ++ i)
        if(q[i].t <= mid && q[i].k == 2) Add(q[i].y, 1);
        else if(q[i].t > mid && q[i].k != 2) ans[q[i].v] += Q(q[i].y) * q[i].k;
    for(int i = L; i <= R; ++ i) {
        if(q[i].t <= mid && q[i].k == 2) Add(q[i].y, -1);
        if(q[i].t <= mid) nq[lp ++] = q[i];
        else nq[rp ++] = q[i];
    }
    for(int i = L; i <= R; ++ i) q[i] = nq[i];
    cdq(L, mid); cdq(mid+1, R);
}
int main() {
    GET(n); GET(m); N = 300000;
    for(int i = 1; i <= n; ++ i) {
        GET(a[i]);
        q[++ cnt] = Querys(cnt, i + a[i], a[i] - i + 160000, 0, 2);
    }
    char op[10]; int u, v;
    for(int i = 1; i <= m; ++ i) {
        scanf("%s", op);
        GET(u); GET(v);
        if('M' == op[0]) {
            a[u] = v;
            q[++ cnt] = Querys(cnt, u + v, v - u + 160000, 0, 2);
        }
        else {
            ++ qn; assert(a[u]-u-v+160000-1 > 0);
            if(u+a[u]-v-1 > 0) q[++ cnt] = Querys(cnt, (u+a[u]-v-1), Min(a[u]-u+v+160000, N), qn, -1);
            if(u+a[u]-v-1 > 0) q[++ cnt] = Querys(cnt, (u+a[u]-v-1), Min(a[u]-u-v+160000-1,N), qn, 1);
            q[++ cnt] = Querys(cnt, (u+a[u]+v), Min(a[u]-u+v+160000,N), qn, 1);
            q[++ cnt] = Querys(cnt, (u+a[u]+v), Min(a[u]-u-v+160000-1,N), qn, -1);
        }
    }
    sort(q+1, q+cnt+1);
    cdq(1, cnt);
    for(int i = 1; i <= qn; ++ i)
        printf("%d\n", ans[i]);
    return 0;
}

树套树版:

#include <cstdio>
#define MAXN 1000005
inline void GET(int &n) {
    char c, f = 1; n = 0;
    do if((c=getchar()) == '-') f = -1; while('0' > c || c > '9');
    do n=n*10+c-'0',c=getchar(); while('0' <= c && c <= '9');
    n *= f;
}
unsigned fix[MAXN * 20], sd = 2333;
int key[MAXN * 20], s[MAXN * 20][2], sz[MAXN * 20], rt[MAXN], cnt, n, m, N, a[MAXN], num[MAXN];
inline unsigned ran() { return sd = sd * sd + sd^27873; }
inline void pushup(int x) {
    sz[x] = sz[s[x][0]] + sz[s[x][1]] + num[x];
}
inline void rot(int &x, bool f) {
    int t = s[x][f]; s[x][f] = s[t][f^1]; s[t][f^1] = x;
    pushup(x); pushup(t); x = t;
}
void Insert(int &x, int v) {
    if(!x) { x = ++ cnt; fix[x] = ran(); num[x] = sz[x] = 1; key[x] = v; return; }
    if(v == key[x]) { ++ num[x]; pushup(x); return; }
    bool f = key[x] < v;
    Insert(s[x][f], v); pushup(x);
    if(fix[s[x][f]] > fix[x]) rot(x, f);
} int u, v, i;
int Rank(int x, int v) {
    if(!x) return 0;
    if(key[x] < v) return sz[x] - sz[s[x][1]] + Rank(s[x][1], v);
    else if(key[x] > v) return Rank(s[x][0], v);
    else return sz[x] - sz[s[x][1]];
}
void Ins(int x, int v) {
    for(; x <= N; x += x&-x)
        Insert(rt[x], v);
}
int Q(int x, int y) {
    int sum = 0;
    for(; x > 0; x -= x&-x)
        sum += Rank(rt[x], y);
    return sum;
}
int main() {
    GET(n); GET(m); N = 2*n + 100000;
    for(int i = 1; i <= n; ++ i) {
        GET(a[i]); Ins(i+a[i], a[i]-i);
    }
    char op[10];
    for(i = 1; i <= m; ++ i) {
        scanf("%s", op);
        GET(u); GET(v);
        if('M' == op[0]) {
            a[u] = v; Ins(u+a[u], a[u]-u);
        }
        else {
            int ans = Q((u+a[u]+v), a[u]-u+v) - Q((u+a[u]-v-1), a[u]-u+v) - Q((u+a[u]+v), a[u]-u-v-1) + Q((u+a[u]-v-1), a[u]-u-v-1);
            printf("%d\n", ans);
        }
    }
    return 0;
}
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值