POJ-2155:Matrix(二维线段树或二维树状数组)


题目链接:点击打开链接


题目大意:

给你一个二维表格,执行两种操作,第一种给你左上角和右下角的坐标,让你把这个矩形内的数翻转,即0变成1,1变成0。第二种操作就是查询某个点当前的值。


解题思路:

这道题有两种做法,分别是二维树状数组和二维线段树。以下分别讲解。


树状数组:

二维树状数组很好理解,代码也非常简单,而且最后耗时貌似也比线段树少不少。用一些简单的操作即可实现题目的要求。

假设题目让你更新 p1,q1 p2,q2 那么可以 将p1,q1,后面的点都加1,然后将不符合题目要求的区域减1,即 p2+1,q1   和   p1,q2+1 . 但是这样的话右下角一部分被重复减了两次,因此再将右下角的部分+1,即 p2+1,q2+1 后面加1.   查询的时候直接查询就可以。以下贴树状数组的代码,


#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
typedef unsigned long long ULL;
int n,m;
char c;
int d[1005][1005];
void add(int i,int j,int x)     //二维树状数组模板,跟一维很像
{
    while(i<=n)
    {
        int jj=j;
        while(jj<=n)
        {
            d[i][jj]+=x;
            jj += jj&-jj;
        }
        i += i&-i;
    }
}
int sum(int i,int j)
{
    int s=0;
    while(i>0)
    {
        int jj=j;
        while(jj>0)
        {
            s=s+d[i][jj];
            jj -= jj&-jj;
        }
        i -= i&-i;
    }
    return s;
}
int main()
{
    int QAQ;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        int p1,q1,p2,q2;
        scanf("%d%d",&n,&m);
        memset(d,0,sizeof(d));    //记得初始化
        for(int i=0;i<m;i++)
        {
            scanf(" %c",&c);
            if(c=='C')
            {
                scanf("%d%d%d%d",&p1,&q1,&p2,&q2);      //将题目要求部分加1
                add(p1,q1,1);
                add(p1,q2+1,-1);
                add(p2+1,q1,-1);
                add(p2+1,q2+1,1);
            }
            if(c=='Q')
            {
                scanf("%d%d",&p1,&q1);
                printf("%d\n",sum(p1,q1)%2);    //输出结果余2
            }
        }
        printf("\n");
    }
}


线段树部分:


线段树要用二维线段树,也是我第一次写的二维线段树。刚开始是真的不知道怎么写。反正瞎写了一种四分树,反正不知道哪错了疯狂RE,而且后来去网上查了一下。

二维线段树貌似有两种,一种就是刚刚说的四分树,但是四分树某些情况下时间复杂度极高。而且我疯狂RE也不知道为啥。后来学习了另外一种树套树的做法。也就是x对应的每个节点也有一颗线段树,刚开始也一直在想维护的问题,后来上网看了别人的博客才发现不用这么搞,而且更新和查询的方法比较奇妙。


更新的话就是先找到符合要求的x的对应区间,其次更新对应的y线段树。但是这样会在查询的时候造成一个问题,例如,第一次更新 1,1  10,10  。那么假设当前区间恰好为1到10,就会更新当前节点的y线段树。但是如果下一次更新1,1  5,5  更新的就是上个节点的子节点的y线段树,y线段树同样也有这样的问题, 所以查询的时候就要注意,不能随意递归下去,那么如果想解决这样的问题的话就在每一次的的x节点处查询y线段树即可,y线段树也是每个节点的值都加上。这样的话就保证了你查询的值没有遗漏,

买买买   

总体来说可能是这道题目偏简单,实现起来不算太麻烦,但是耗时比树状数组多好多,而且之前写线段树习惯用结构体写,换成这种线段树突然不会用结构体写了,最后忘初始化wa了好多发,可能是我代码太丑的原因。。。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define xlson xl,xmid,xt<<1
#define xrson xmid+1,xr,xt<<1|1
#define ylson yl,ymid,xt,yt<<1
#define yrson ymid+1,yr,xt,yt<<1|1
using namespace std;
typedef long long ll;
typedef unsigned long long ULL;
const int maxn=1010;
int n,m,ans;
int tree[maxn<<2][maxn<<2];
void update_y(int q1,int q2,int yl,int yr,int xt,int yt)    //更新y线段树
{
    if(q1<=yl&&yr<=q2)
    {
        tree[xt][yt]++;
        return ;
    }
    int ymid=(yl+yr)>>1;
    if(q1<=ymid)
        update_y(q1,q2,ylson);
    if(q2>ymid)
        update_y(q1,q2,yrson);
}
void update_x(int p1,int q1,int p2,int q2,int xl,int xr,int xt)     //查找符合要求的区间
{
    if(p1<=xl&&xr<=p2)      //找到合适的区间就更新y线段树
    {
        update_y(q1,q2,1,n,xt,1);
        return ;
    }
    int xmid=(xl+xr)>>1;
    if(p1<=xmid)
        update_x(p1,q1,p2,q2,xlson);
    if(p2>xmid)
        update_x(p1,q1,p2,q2,xrson);
}
void query_y(int y,int yl,int yr,int xt,int yt)     //查询y线段树中的值
{
    ans=ans+tree[xt][yt];       //将每个值都加上防止遗漏
    if(yl==yr) return ;
    int ymid=(yl+yr)>>1;
    if(y<=ymid)
        query_y(y,ylson);
    if(y>ymid)
        query_y(y,yrson);
}
void query_x(int x,int y,int xl,int xr,int xt)      //查询所有的x线段树
{
    query_y(y,1,n,xt,1);        //每个x节点都查询y线段树
    if(xl==xr) return ;
    int xmid=(xl+xr)>>1;
    if(x<=xmid)
        query_x(x,y,xlson);
    if(x>xmid)
        query_x(x,y,xrson);
}
int main()
{
    int QAQ;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        memset(tree,0,sizeof(tree));    //记得初始化
        scanf("%d%d",&n,&m);
        char q;
        int p1,q1,p2,q2;
        for(int i=0;i<m;i++)
        {
            scanf(" %c",&q);
            if(q=='C')
            {
                scanf("%d%d%d%d",&p1,&q1,&p2,&q2);
                update_x(p1,q1,p2,q2,1,n,1);
            }
            if(q=='Q')
            {
                scanf("%d%d",&p1,&q1);
                ans=0;
                query_x(p1,q1,1,n,1);
                printf("%d\n",ans%2);
            }
        }
        printf("\n");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值