bzoj 1176: [Balkan2007]Mokia (CDQ 分治, 两种方法的 )

描述:

维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的总权值.修改操作数M<=160000,询问数Q<=10000,W<=2000000.

思路:

三维,时间一维,x轴一维,y轴一维,

按照一维排序之后,然后用左区间更新右区间;树状数组进行维护.

这里有两种写法,

一种是 用时间分治,一种是用 x 轴分治,

用时间分治:

这种用时间分治的方法,在CDQ中不需要在排序了, 这样子省下了不少时间.

为什么不需要排序?

我们先按照x轴排序,时间在区间里是.乱序的,然后时间在 [l,mid] 中的算左区间,[mid+1, r] 的算右区间.

区间 从 l 到 r ,那么时间也是从 l 到 r, 当我们递归每一层的时候,要把时间和 l , r 对应上.

#include<bits/stdc++.h>
#define low(x) (x & (-x))
using namespace std;
const int N = 2e5+100;
const int M = 2e6+10;
struct node{
    int id,qid,x,y,c;
}f[N*4],g[N*4];
bool cmp(node A, node B){
    if (A.x != B.x) return A.x < B.x;
    if (A.y != B.y) return A.y < B.y;
    return A.id < B.id;
}
int n,m,s,tim,qtim,ans[N];
int c[M];
void add(int x, int y){
    for (; x <= m; x += low(x)) c[x] += y;
}
int sum(int x){
    int ans = 0;
    for (; x; x -= low(x)) ans += c[x];
    return ans;
}
void CDQ(int l, int r){
    if (l >= r) return;
    int mid = (l + r) >> 1;
    for (int i = l; i <= r; ++i){
        if (f[i].id <= mid && f[i].qid == -1) add(f[i].y,f[i].c); else
        if (f[i].id > mid && f[i].qid != -1) ans[f[i].qid] += f[i].c*sum(f[i].y);
    }
    for (int i = l; i <= r; ++i)
        if (f[i].id <= mid && f[i].qid == -1) add(f[i].y,-f[i].c);
    int x = l, y = mid; 
    for (int i = l; i <= r; ++i)
        if (f[i].id <= mid) g[x++] = f[i]; else g[++y] = f[i];
    for (int i = l; i <= r; ++i)
        f[i] = g[i];
    CDQ(l,mid); CDQ(mid + 1, r); 
}
int main(){
    scanf("%d%d",&s,&m);
    int op,x,y,xx,yy,z;
    while(scanf("%d",&op)){
        if (op == 3) break;
        if (op == 1){
            scanf("%d%d%d",&x,&y,&z);
            f[++tim] = (node){tim,-1,x,y,z};
        } else{
            scanf("%d%d%d%d",&x,&y,&xx,&yy);
            f[++tim] = (node){tim,qtim,xx,yy,1};
            f[++tim] = (node){tim,qtim,x-1,yy,-1};
            f[++tim] = (node){tim,qtim,xx,y-1,-1};
            f[++tim] = (node){tim,qtim++,x-1,y-1,1};
        }
    }
    sort(f+1,f+tim+1,cmp);
    CDQ(1,tim);
    for (int i = 0; i < qtim; ++i)
        printf("%d\n",ans[i]);
    return 0;
}


/*
0 4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3
*/

 

 第二种写法实现就时间长了一点,

因为在 cdq中排序了,在 cdq 中,我们就是 [l,mid] 是左区间, [mid+1,r] 是右区间.用左区间去更新右区间,

然后这个时候是有序的,我们要以 x 轴为关键字排序,然后打乱区间.再进行更新.

#include<bits/stdc++.h>
#define low(x) (x & (-x))
using namespace std;
const int N = 2e5+100;
const int M = 2e6+10;
struct node{
    int id,qid,x,y,c;
    bool vis;
}f[N*4],g[N*4];
bool cmp(node A, node B){
    if (A.x != B.x) return A.x < B.x;
    if (A.y != B.y) return A.y < B.y;
    return A.vis < B.vis;
}
int n,m,s,tim,qtim,ans[N];
int c[M];
void add(int x, int y){
    for (; x <= m; x += low(x)) c[x] += y;
}
int sum(int x){
    int ans = 0;
    for (; x; x -= low(x)) ans += c[x];
    return ans;
}
void CDQ(int l, int r){
    if (l >= r) return;
    int mid = (l + r) >> 1;
    for (int i = l; i <= mid; ++i)
        g[i] = f[i], g[i].vis = 0;
    for (int i = mid+1; i <= r; ++i)
        g[i] = f[i], g[i].vis = 1;
    sort(g+l,g+r+1,cmp);
    for (int i = l; i <= r; ++i)
        if (g[i].vis == 0 && g[i].qid == -1) add(g[i].y,g[i].c); else
        if (g[i].vis == 1 && g[i].qid != -1) ans[g[i].qid] += g[i].c*sum(g[i].y);

    for (int i = l; i <= r; ++i)
        if (g[i].vis == 0 && g[i].qid == -1) add(g[i].y,-g[i].c);
    CDQ(l,mid); CDQ(mid + 1, r); 
}


int main(){
    scanf("%d%d",&s,&m);
    int op,x,y,xx,yy,z;
    while(scanf("%d",&op)){
        if (op == 3) break;
        if (op == 1){
            scanf("%d%d%d",&x,&y,&z);
            f[++tim] = (node){tim,-1,x,y,z};
        } else{
            scanf("%d%d%d%d",&x,&y,&xx,&yy);
            f[++tim] = (node){tim,qtim,xx,yy,1};
            f[++tim] = (node){tim,qtim,x-1,yy,-1};
            f[++tim] = (node){tim,qtim,xx,y-1,-1};
            f[++tim] = (node){tim,qtim++,x-1,y-1,1};
        }
    }
    CDQ(1,tim);
    for (int i = 0; i < qtim; ++i)
        printf("%d\n",ans[i]);
    return 0;
}


/*
0 4
1 2 3 3
2 1 1 3 3
1 2 2 2
2 2 2 3 4
3

*/

总结一下:

自我认为,第二种方法,是一个通用的方法,

然后第一种方法,分治的数组的值,一定要不重复,而且对应区间.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值