P2086 [NOI2012] 魔幻棋盘 题解

395 篇文章 1 订阅
34 篇文章 0 订阅

[NOI2012] 魔幻棋盘

题目描述

将要读二年级的小 Q 买了一款新型益智玩具——魔幻棋盘,它是一个 N N N M M M 列的网格棋盘,每个格子中均有一个正整数。棋盘守护者在棋盘的第 X X X 行第 Y Y Y 列(行与列均从 1 1 1 开始编号)并且始终不会移动。棋盘守护者会进行两种操作:

  • (a)询问:他会以自己所在位置为基础,向四周随机扩展出一块大小不定的矩形区域,向你询问这一区域内所有数的最大公约数是多少。
  • (b)修改:他会随意挑选棋盘上的一块矩形区域,将这一区域内的所有数同时加上一个给定的整数。

游戏说明书上附有这样一句话“聪明的小朋友,当你连续答对 19930324 19930324 19930324 次询问后会得到一个惊喜噢!”。小 Q 十分想得到这个惊喜,于是每天都在玩这个玩具。但由于他粗心大意,经常算错数,难以达到这个目标。于是他来向你寻求帮助,希望你帮他写一个程序来回答棋盘守护者的询问,并保证 100 % 100\% 100% 的正确率。

为了简化问题,你的程序只需要完成棋盘守护者的 T T T 次操作,并且问题保证任何时刻棋盘上的数字均为不超过 2 62 − 1 2^{62} - 1 2621 的正整数。

输入格式

第一行为两个正整数 N , M N,M N,M,表示棋盘的大小。

第二行为两个正整数 X , Y X,Y X,Y,表示棋盘守护者的位置。

第三行仅有一个正整数 T T T,表示棋盘守护者将进行 T T T 次操作。

接下来 N N N 行,每行有 M M M 个正整数,用来描述初始时棋盘上每个位置的数。

接下来 T T T 行,按操作的时间顺序给出 T T T 次操作。每行描述一次操作,以一个数字 0 0 0 1 1 1 开头:

  • 若以数字 0 0 0 开头,表示此操作为询问,随后会有四个非负整数 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2,表示询问的区域是以棋盘守护者的位置为基础向上扩展
    x 1 x_1 x1 行,向下扩展 x 1 x_1 x1 行,向左扩展 y 1 y_1 y1 列,向右扩展 y 2 y_2 y2 列得到的矩形区域(详见样例)。
  • 若以数字 1 1 1 开头,表示此操作为修改,随后会有四个正整数 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2 和一个整数 c c c,表示修改区域的上、下边界分别为第 x 1 , x 2 x_1,x_2 x1,x2 行,左、右边界分别为第 y 1 , y 2 y_1,y_2 y1,y2 列(详见样例),在此矩形区域内的所有数统一加上 c c c(注意 c c c 可能为负数)。

输出格式

对于每次询问操作,每行输出一个数,表示该区域内所有数的最大公约数。

样例 #1

样例输入 #1

2 2
1 1
4
6 12
18 24
0 0 0 1 0
1 1 1 1 2 6
1 2 1 2 2 6
0 0 0 1 1

样例输出 #1

6
6

提示

对于第一、第四次操作(查询操作)后,加粗部分表示查询区域。

对于第二、第三次操作(修改操作)后,加粗部分表示修改区域。

测试数据分为 A、B、C 三类:

A 类数据占 20 % 20\% 20%,满足 N ≤ 100 N \leq 100 N100 M ≤ 100 M \leq 100 M100 T ≤ 2 × 1 0 4 T \leq 2\times 10^4 T2×104

B 类数据占 40 % 40\% 40%,满足 N = 1 N = 1 N=1 M ≤ 5 × 1 0 5 M \leq 5\times 10^5 M5×105 T ≤ 1 0 5 T \leq 10^5 T105

C 类数据占 40 % 40\% 40%,满足 N × M ≤ 5 × 1 0 5 N \times M \leq 5\times 10^5 N×M5×105 T ≤ 1 0 5 T \leq 10^5 T105

在每类数据中,均有 50 % 50\% 50% 的数据满足每次修改操作仅含一个格子(即 x 1 = x 2 x_1 = x_2 x1=x2 y 1 = y 2 y_1 = y_2 y1=y2)。

输入数据保证满足题目描述中的所有性质。

先考虑只有一维的情况,要求支持区间加和求区间 gcd⁡\gcdgcd,根据 gcd⁡\gcdgcd 的性质,发现:

gcd⁡(a1,a2,a3,…an)=gcd⁡(ai,a2−a1,a3−a2,…an−an−1) \gcd(a_1,a_2,a_3,\ldots a_n)=\gcd(a_i,a_2-a_1,a_3-a_2,\ldots a_n-a_{n-1}) gcd(a1,a2,a3,an)=gcd(ai,a2a1,a3a2,anan1)

其中 aia_iai 为原序列 aaa 中的任意一个元素,其与序列 aaa 的差分序列的 gcd⁡\gcdgcd 即为原序列的 gcd⁡\gcdgcd。根据该性质,对于一维的情况,就可以通过线段树单点修改维护差分序列,区间查询 gcd⁡\gcdgcd 来实现了。对于 aia_iai,根据修改维护其当前值即可。

然后考虑二维的情况,比如对于这样的一个 4×44 \times 44×4 的矩形:

a1,1a1,2a1,3a1,4a2,1a2,2a2,3a2,4a3,1a3,2a3,3a3,4a4,1a4,2a4,3a4,4\begin{aligned} a_{1,1}\quad a_{1,2}\quad a_{1,3}\quad a_{1,4} \\ a_{2,1}\quad a_{2,2}\quad a_{2,3}\quad a_{2,4} \\ a_{3,1}\quad a_{3,2}\quad a_{3,3}\quad a_{3,4} \\ a_{4,1}\quad a_{4,2}\quad a_{4,3}\quad a_{4,4} \end{aligned} a1,1a1,2a1,3a1,4a2,1a2,2a2,3a2,4a3,1a3,2a3,3a3,4a4,1a4,2a4,3a4,4

设棋盘守护者的位置为 (x,y)(x,y)(x,y),先对每一行进行差分,得:

a1,ya1,2−a1,1a1,3−a1,2a1,4−a1,3a2,ya2,2−a2,1a2,3−a2,2a2,4−a2,3a3,ya3,2−a3,1a3,3−a3,2a3,4−a3,3a4,ya4,2−a4,1a4,3−a4,2a4,4−a4,3\begin{aligned} a_{1,y}\quad a_{1,2}-a_{1,1}\quad a_{1,3}-a_{1,2}\quad a_{1,4}-a_{1,3} \\ a_{2,y}\quad a_{2,2}-a_{2,1}\quad a_{2,3}-a_{2,2}\quad a_{2,4}-a_{2,3} \\ a_{3,y}\quad a_{3,2}-a_{3,1}\quad a_{3,3}-a_{3,2}\quad a_{3,4}-a_{3,3} \\ a_{4,y}\quad a_{4,2}-a_{4,1}\quad a_{4,3}-a_{4,2}\quad a_{4,4}-a_{4,3} \end{aligned} a1,ya1,2a1,1a1,3a1,2a1,4a1,3a2,ya2,2a2,1a2,3a2,2a2,4a2,3a3,ya3,2a3,1a3,3a3,2a3,4a3,3a4,ya4,2a4,1a4,3a4,2a4,4a4,3

对每一行来维护差分序列,根据每一行的 gcd⁡\gcdgcd,就能求得整个矩形的 gcd⁡\gcdgcd。发现这样实现复杂度依然无法接受,于是对每一列也进行差分,也就是进行二维差分,得:

ax,yax,2−ax,1ax,3−ax,2ax,4−ax,3a2,y−a1,ya2,2−a2,1−a1,2+a1,1a2,3−a2,2−a1,3+a1,2a2,4−a2,3−a1,4+a1,3a3,y−a2,ya3,2−a3,1−a2,2+a2,1a3,3−a3,2−a2,3+a2,2a3,4−a3,3−a2,4+a2,3a4,y−a3,ya4,2−a4,1−a3,2+a3,1a4,3−a4,2−a3,3+a3,2a4,4−a4,3−a3,4+a3,3\begin{aligned} &a_{x,y}\qquad\qquad\qquad\quad a_{x,2}-a_{x,1}\qquad\qquad\qquad\quad a_{x,3}-a_{x,2}\qquad\qquad\qquad\quad a_{x,4}-a_{x,3} \\\\ &a_{2,y}-a_{1,y}\qquad a_{2,2}-a_{2,1}-a_{1,2}+a_{1,1}\qquad a_{2,3}-a_{2,2}-a_{1,3}+a_{1,2}\qquad a_{2,4}-a_{2,3}-a_{1,4}+a_{1,3} \\\\ &a_{3,y}-a_{2,y}\qquad a_{3,2}-a_{3,1}-a_{2,2}+a_{2,1}\qquad a_{3,3}-a_{3,2}-a_{2,3}+a_{2,2}\qquad a_{3,4}-a_{3,3}-a_{2,4}+a_{2,3} \\\\ &a_{4,y}-a_{3,y}\qquad a_{4,2}-a_{4,1}-a_{3,2}+a_{3,1}\qquad a_{4,3}-a_{4,2}-a_{3,3}+a_{3,2}\qquad a_{4,4}-a_{4,3}-a_{3,4}+a_{3,3} \end{aligned} ax,yax,2ax,1ax,3ax,2ax,4ax,3a2,ya1,ya2,2a2,1a1,2+a1,1a2,3a2,2a1,3+a1,2a2,4a2,3a1,4+a1,3a3,ya2,ya3,2a3,1a2,2+a2,1a3,3a3,2a2,3+a2,2a3,4a3,3a2,4+a2,3a4,ya3,ya4,2a4,1a3,2+a3,1a4,3a4,2a3,3+a3,2a4,4a4,3a3,4+a3,3

发现二维差分后的这个矩形的 gcd⁡\gcdgcd 即为原矩形的 gcd⁡\gcdgcd。然后考虑维护,对于第一行和第一列,去掉位置 (1,1)(1,1)(1,1)ax,ya_{x,y}ax,y 后剩下的序列,其为一维上的问题,用两个一维线段树维护即可。对于去掉第一行和第一列的剩下的矩形,用二维线段树维护即可。两者都是只用支持单点修改和区间查询。对于 ax,ya_{x,y}ax,y,根据修改维护其当前值即可。

二维线段树可以用树套树或者四分树来实现,因为树套树的空间不好处理,并且题目中也说明了查询是随机的,所以我这里用四分树来实现二维线段树了。

code:code:code:

#include<bits/stdc++.h>
#define maxn 2000010
#define maxm 32000010
#define a(i,j) a[(i-1)*m+j]
#define b(i,j) b[(i-1)*m+j]
#define ls (cur<<1)
#define rs (cur<<1|1)
#define mid ((l+r)>>1)
#define midx ((u+d)>>1)
#define midy ((l+r)>>1)
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
    x=0;char c=getchar();bool flag=false;
    while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    if(flag)x=-x;
}
int n,m,sx,sy,q,root,rt=1,tree_cnt;
int uls[maxm],urs[maxm],dls[maxm],drs[maxm];
ll w;
ll a[maxn],b[maxn],val[maxm];
ll gcd(ll a,ll b)
{
    return b?gcd(b,a%b):abs(a);
}
void build(int u,int d,int l,int r,int &cur)
{
    if(u>d||l>r) return;
    if(!cur) cur=++tree_cnt;
    if(u==d&&l==r)
    {
        val[cur]=b(u,l);
        return;
    }
    build(u,midx,l,midy,uls[cur]),build(u,midx,midy+1,r,urs[cur]);
    build(midx+1,d,l,midy,dls[cur]),build(midx+1,d,midy+1,r,drs[cur]);
    val[cur]=gcd(gcd(val[uls[cur]],val[urs[cur]]),gcd(val[dls[cur]],val[drs[cur]]));
}
void modify(int u,int d,int l,int r,int x,int y,ll v,int cur)
{
    if(l==r&&u==d)
    {
        val[cur]+=v;
        return;
    }
    if(x<=midx)
    {
        if(y<=midy) modify(u,midx,l,midy,x,y,v,uls[cur]);
        else modify(u,midx,mid+1,r,x,y,v,urs[cur]);
    }
    else
    {
        if(y<=midy) modify(midx+1,d,l,midy,x,y,v,dls[cur]);
        else modify(midx+1,d,midy+1,r,x,y,v,drs[cur]);
    }
    val[cur]=gcd(gcd(val[uls[cur]],val[urs[cur]]),gcd(val[dls[cur]],val[drs[cur]]));
}
ll query(int U,int D,int L,int R,int u,int d,int l,int r,int cur)
{
    if(U>D||L>R) return 0;
    if(U<=u&&D>=d&&L<=l&&R>=r) return val[cur];
    ll v=0;
    if(U<=midx)
    {
        if(L<=midy) v=gcd(v,query(U,D,L,R,u,midx,l,midy,uls[cur]));
        if(R>midy) v=gcd(v,query(U,D,L,R,u,midx,midy+1,r,urs[cur]));
    }
    if(D>midx)
    {
        if(L<=midy) v=gcd(v,query(U,D,L,R,midx+1,d,l,midy,dls[cur]));
        if(R>midy) v=gcd(v,query(U,D,L,R,midx+1,d,midy+1,r,drs[cur]));
    }
    return v;
}
struct Segment_Tree
{
    ll a[maxn],val[maxn];
    void build(int l,int r,int cur)
    {
        if(l==r)
        {
            val[cur]=a[l];
            return;
        }
        build(l,mid,ls),build(mid+1,r,rs);
        val[cur]=gcd(val[ls],val[rs]);
    }
    void modify(int l,int r,int pos,ll v,int cur)
    {
        if(l==r)
        {
            val[cur]+=v;
            return;
        }
        if(pos<=mid) modify(l,mid,pos,v,ls);
        else modify(mid+1,r,pos,v,rs);
        val[cur]=gcd(val[ls],val[rs]);
    }
    ll query(int L,int R,int l,int r,int cur)
    {
        if(L>R) return 0;
        if(L<=l&&R>=r) return val[cur];
        ll v=0;
        if(L<=mid) v=gcd(v,query(L,R,l,mid,ls));
        if(R>mid) v=gcd(v,query(L,R,mid+1,r,rs));
        return v;
    }
}T1,T2;
int main()
{
    read(n),read(m),read(sx),read(sy),read(q);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            read(a(i,j));
    for(int i=1;i<n;++i)
        for(int j=1;j<m;++j)
            b(i,j)=a(i+1,j+1)-a(i,j+1)-a(i+1,j)+a(i,j);
    for(int i=1;i<n;++i) T1.a[i]=a(i+1,sy)-a(i,sy);
    for(int i=1;i<m;++i) T2.a[i]=a(sx,i+1)-a(sx,i);
    w=a(sx,sy),build(0,n,0,m,root),T1.build(0,n,root),T2.build(0,m,root);
    while(q--)
    {
        int u,d,l,r,opt;
        ll v;
        read(opt),read(u),read(l),read(d),read(r);
        if(!opt)
        {
            u=sx-u,l=sy-l,d=sx+d,r=sy+r;
            v=gcd(w,query(u,d-1,l,r-1,0,n,0,m,root));
            v=gcd(v,gcd(T1.query(u,d-1,0,n,rt),T2.query(l,r-1,0,m,rt)));
            printf("%lld\n",v);
        }
        else
        {
            read(v);
            if(sx>=u&&sx<=d&&sy>=l&&sy<=r) w+=v;
            if(sy>=l&&sy<=r) T1.modify(0,n,u-1,v,rt),T1.modify(0,n,d,-v,rt);
            if(sx>=u&&sx<=d) T2.modify(0,m,l-1,v,rt),T2.modify(0,m,r,-v,rt);
            modify(0,n,0,m,u-1,l-1,v,root),modify(0,n,0,m,d,r,v,root);
            modify(0,n,0,m,u-1,r,-v,root),modify(0,n,0,m,d,l-1,-v,root);
        }
    }
    return 0;
}
  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只贴代码君(yaosicheng)

帅帅的你,留下你的支持吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值