bzoj 3249 [ioi2013]game(线段树)(伸展树)

刷题之路 同时被 3 个专栏收录
137 篇文章 0 订阅
10 篇文章 0 订阅
4 篇文章 0 订阅

题目

Bazza 和 Shazza 正在玩游戏。游戏在一个 R 行 C 列的网格上进行。
其中, R 行 编号为 0, …, R - 1 , C 列编号为 0, …, C - 1 。
我们用 (P, Q) 表示位于 P 行 Q 列的单 元格。
每个单元格包含一个非负整数,游戏开始时所有单元格内的整数均为零。 
游戏如下进行:任意时刻,Bazza 可以做如下动作之一: 修改一个单元格 (p, q) 内包含的整数值; 
要求 Shazza 计算一个给定子矩阵中所有单元格内数字的最大公约数 (GCD),子矩阵的两个对角分别为 (p, q) 和 (u, v) (子矩阵包含给定的两 个对角点)。 
Bazza会做 N + N 次动作(其中,修改单元格内数据 N 次,询问GCD N 次) 。 
你的任务是对 Bazza 提出的问题给出正确答案。

特性

gcd满足结合性,即gcd(a,b,c,d) = gcd( gcd(a,b) , gcd(c,d) )。这个很像加减法 a+b+c+d = (a+b)+(c+d),这东西可以用线段树维护。

题解

线段树+伸展树
类比的想,gcd也可以用线段树来维护,我们称其为gcd和。
那么一个朴素的想法就是先纵向建一棵线段树,对于每个纵向的区间再开一棵其区间范围对应的线段树,后者负责处理gcd和。
但是这么做会爆空间,无语,果然是IOI的题,哪有这么简单。
这时,伸展树就蹦出来了,因为伸展树支持动态开点啊。
所以这题就是树套树咯~

总结

这是我第一次把伸展树当线段树使用。
本质上,伸展树与线段树是一样的,因为他们都是二叉树。之前也做过一些二叉树求和呀之类的题,不过现在我才认识到伸展树完全可以替代线段树。
动态开点是伸展树的第一大优势,根据中序遍历建树,也就是说要满足x->l->val < x->val < x->r->val。伸展树允许x->l->val,x->val,x->l->val不连续,所以能节省空间;还有伸展树不像线段树一样有那么多“上司”,它们像树状数组一样,每个点都是代表了一个位置。此外,他也能保证一个loc能找到满足上式的一个位置。
与线段树的sum相对应的,伸展树的sum与线段树略有不同,两者都是说子树的gcd和,但是线段树的更加代表的区间固定一些,而且线段树的sum在xl!=xr的情况下对应的是空位置,而伸展树是一个实际存在的点。这导致伸展树与线段树的求解函数有所不同。

代码

讲几句吧,免得我自己都看不懂...
每个位置的点都有一个独一无二的id=(y*n+x),inf表示最大的id。
为什么id是竖着排呢?这与我们竖着建线段树有关。在伸展树中,id的范围仍是[n+1,inf],如果我们要求的是第y1到y2列的和,我们就可以求伸展树中编号在[(y1-1)*n+1,y2*n]的gcd和,这样相当于求的是[id(1,y1),id(n,y2)]的gcd和。不过没有关系,行的问题线段树会帮我们限制好,他会让你在只包含[lx,rx]的行中求上面这个东西,把行的限制条件加上,就成了[id(lx,y1),id(rx,y2)]。
代码使用指针实现的,嘻嘻我也是第一次这么打,感觉很精简。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=705000;

inline ll read()
{
    ll re=0;bool f=false;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') re=re*10+(ch^48),ch=getchar();
    return f?-re:re;
}

inline ll gcd(ll a,ll b)
{
    if(!a && !b) return 0;
    if(!a) return b;
    if(!b) return a;
    return gcd(b,a%b);
}

int R,tot=0,l[maxn],r[maxn];
ll cl,cr,loc,inf,ans,w,x1,y1,x2,y2;

//*****************伸展树*******************
struct node
{
    int p;ll val,v,sum;
    node *l,*r;
    void up(){sum=gcd(v,gcd(l->sum,r->sum));}//更新操作 
}*blank=new(node),*T[maxn];

void Rotatel(node *&x)
{
    node *y=x->r;
    x->r=y->l;
    y->l=x;
    x->up();
    y->up();
    x=y;
}
void Rotater(node *&x)
{
    node *y=x->l;
    x->l=y->r;
    y->r=x;
    x->up();
    y->up();
    x=y;
}

void Ins(node *&x)
{
    if(x==blank)
    {
        x=new(node);
        x->val=loc;
        x->l=x->r=blank;
        x->v=x->sum=w;
        x->p=rand();//随机数,使树更平衡(规定:父节点的p >= 子节点的p) 
        return ;
    }
    if(loc==x->val)
    {
        x->v=w;
        x->up();
    }
    else if(loc<x->val)
    {
        Ins(x->l);
        if(x->p < x->l->p) Rotater(x);
        else x->up();
    }
    else
    {
        Ins(x->r);
        if(x->p < x->r->p) Rotatel(x);
        else x->up();
    }
}

void Ask(node *&x,ll lx,ll rx)
{
    if(x==blank) return ;
    if(cl<=lx && rx<=cr)
    {
        ans=gcd(ans,x->sum);
        return ;
    }
    if(cl<=x->val && x->val<=cr) ans=gcd(ans,x->v);
    if(cl<x->val) Ask(x->l,lx,x->val-1);
    if(x->val<cr) Ask(x->r,x->val+1,rx);
}

//*****************线段树*******************
void change(int &x,int lx,int rx)
{
    if(!x) x=++tot,T[x]=blank;
    Ins(T[x]);
    if(lx==rx) return ;
    int mid=lx+rx>>1;
    if(x1<=mid) change(l[x],lx,mid);
    else change(r[x],mid+1,rx);
}

void ask(int x,int lx,int rx)
{
    if(!x) return ;
    if(x1<=lx && rx<=x2)
    {
        Ask(T[x],1,inf);
        return ;
    }
    int mid=lx+rx>>1;
    if(x1<=mid) ask(l[x],lx,mid);
    if(mid<x2) ask(r[x],mid+1,rx);
}

int main()
{
    blank->l=blank->r=blank;
    ll n=read(),m=read(),q=read();
    inf=(m+1)*n;
    while(q--)
    {
        int opt=read();
        if(opt==1)
        {
            x1=read()+1,y1=read()+1,w=read();
            loc=y1*n+x1;
            change(R,1,n);
        }
        else
        {
            x1=read()+1,y1=read()+1;
            x2=read()+1,y2=read()+1;
            cl=y1*n+1,cr=(y2+1)*n;
            ans=0;ask(R,1,n);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值