题目
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;
}