[51nod1768]Rikka with Sequences

题目描述

众所周知,萌萌哒六花不擅长数学,所以勇太给了她一些数学问题做练习,其中有一道是这样的:
勇太有一个长度为 n 的数组A 和一个大小为n×n 的二维数组B,最开始 Bi,j=jk=iAk
接下来勇太进行了两种操作:
1. 给出两个整数 l 和 x ,把 A_l 的值变成x 。
2. 给出两个整数 l 和 r ,保证 l<r ,表示询问 Bl,r 的值。
在每次操作之后,勇太都会进行一次更新: Bi,j=min(Bi,j,jk=iAk)
为了锻炼六花的计算能力,勇太希望六花能够尽可能快地回答他的问题。
当然,这个问题对于萌萌哒六花来说实在是太难了,你可以帮帮她吗?
输入数据保证 n,m≤105 , A_i,x≤109

分析

历史最小区间和无法用普通线段树实现,我们换个思路。
考虑维护B数组,把i看做x坐标,j看做y坐标,它就是二维空间的点集嘛,使用二维线段树维护?然而历史标记不能拆分,打起来也很麻烦。
考虑使用KD树,怎么用呢?使用KD树常用小技巧,离线先,把所有询问点抽出来建立KD树,然后考虑每个操作的影响。改变一个a_x,就相当于区间加减一堆点的值,这个区间的左下角是(1,x),右上角是(x,n);查询相当于查询一个点的历史最小值。
用吉如一论文经典维护历史标记做法即可。
KD树区间打标记的时候,KD树控制的区间和标记区间相离则退出,完全包含则打区间标记;否则递归下传,注意判断当前KD树实际代表点是否被覆盖,若覆盖则直接把改变加上去。
那么复杂度是 O(nn) 的,具体证明见KD树课件。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
#include<map>
using namespace std;
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=4e5+5,mo=998244353,Q=1e6+5; 
struct rec
{
    int s,x,y,id,pid;
}que[N];
struct kd_tree
{
    int lx,ly,rx,ry;
    int x,y,ls,rs;
    ll v,mnv,tag,mntag;
}tr[N];
struct vec
{
    int x,y;
}d[N];
int ws,m,rt,lx,ly,rx,ry,a[N],n,i,td,x,v,ax,ay,tp;
ll prt[N];
multiset<vec> trs;
bool cmp(vec a,vec b)
{
    if (!ws) return a.x<b.x;
    return a.y<b.y;
}
bool operator <(vec a,vec b)
{
    return a.x<b.x||a.x==b.x&&a.y<b.y;
}
int w[20],I;
void Print(ll x)
{
    w[0]=0;
    while (x) w[++w[0]]=x%10,x/=10;
    if (!w[0]) putchar('0');
    fd(I,w[0],1) putchar(w[I]+'0');
    putchar('\n');
}
void mt(int x,int y)
{
    cmax(tr[x].rx,tr[y].rx);
    cmax(tr[x].ry,tr[y].ry);
    cmin(tr[x].lx,tr[y].lx);
    cmin(tr[x].ly,tr[y].ly);
}
int make(int l,int r,int s)
{
    ws=s;
    int m=(l+r)/2;
    nth_element(d+l,d+m,d+r+1,cmp);
    s^=1;
    tr[m].lx=tr[m].rx=tr[m].x=d[m].x;
    tr[m].ly=tr[m].ry=tr[m].y=d[m].y;
    if (l<m) mt(m,tr[m].ls=make(l,m-1,s));
    if (m<r) mt(m,tr[m].rs=make(m+1,r,s));
    return m;
}
void down(int x)
{
    int ls=tr[x].ls,rs=tr[x].rs;
    cmin(tr[ls].mntag,tr[x].mntag+tr[ls].tag);
    cmin(tr[rs].mntag,tr[x].mntag+tr[rs].tag);
    cmin(tr[x].mnv,tr[x].v+tr[x].mntag);
    tr[ls].tag+=tr[x].tag;
    tr[rs].tag+=tr[x].tag;
    tr[x].v+=tr[x].tag;
    tr[x].tag=tr[x].mntag=0;
}
void change(int x)
{
    kd_tree &y=tr[x];
    if (!x||y.rx<lx||y.lx>rx||y.ry<ly||y.ly>ry) return ;
    if (lx<=y.lx&&y.rx<=rx&&ly<=y.ly&&y.ry<=ry)// all in
    {
        y.tag+=v;
        cmin(y.mntag,y.tag);
        return ; 
    }
    down(x);
    if (lx<=y.x&&y.x<=rx&&ly<=y.y&&y.y<=ry) // this in
    {
        y.v+=v;
        cmin(y.mnv,y.v);
    }
    change(y.ls);
    change(y.rs);
}
void thr(int x)
{
    if (!x) return ;
    down(x);
    tr[x].mnv=tr[x].v;
    thr(tr[x].ls);
    thr(tr[x].rs);
}
ll get(int x)
{
    if (!x||ax<tr[x].lx||ax>tr[x].rx||ay<tr[x].ly||ay>tr[x].ry) return 1e15;
    down(x);
    if (tr[x].x==ax&&tr[x].y==ay) return tr[x].mnv;
    return min(get(tr[x].ls),get(tr[x].rs));
}
void predo()
{
    rt=make(1,td,0);
    fo(i,1,n)
    {
        lx=1;rx=i;ly=i;ry=n;v=a[i];
        change(rt);
    }
    thr(rt);
}
int main()
{
    freopen("t1.in","r",stdin);
    freopen("t1.out","w",stdout);
    scanf("%d %d",&n,&m);
    fo(i,1,n) scanf("%d",a+i);
    fo(i,1,m)
    {
        scanf("%d %d %d",&que[i].s,&que[i].x,&que[i].y);
        if (que[i].s==2)
        {
            d[++td].x=que[i].x;
            d[td].y=que[i].y;
            if (trs.find(d[td])!=trs.end()) 
                td--;
            else trs.insert(d[td]);
        }
    }
    predo();// range from 1~n,1~n
    fo(i,1,m)
    {
        if (que[i].s==1)
        {
            x=que[i].x;
            lx=1;rx=x;ly=x;ry=n;
            v=que[i].y-a[x];
            change(rt);
            a[x]=que[i].y;
        }else
        {
            ax=que[i].x;ay=que[i].y;
            prt[++tp]=get(rt);
        }
    }
    fo(i,1,tp) Print(prt[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值