[国家集训队]矩阵乘法

一、题目

点此看题

二、解法

整体二分的经典题。

把询问和修改(包括初始值)按时间顺序排好,我们维护区间 [ q l , q r ] [ql,qr] [ql,qr]的答案为 [ l , r ] [l,r] [l,r],每次取一个中间值 m i d mid mid,扫描区间,如果是修改,考虑修改的值是不是大于 m i d mid mid,如果大于 m i d mid mid在二维树状数组上修改,插入右边;否则直接插入左边。

如果是询问,考虑查出来的个数如果小于 k k k的大小,那么把 k k k减去这个个数之后归类到左边;否则归类到右边。然后左边的答案是 [ l , m i d ] [l,mid] [l,mid],右边的答案是 [ m i d + 1 , r ] [mid+1,r] [mid+1,r],当 l = r l=r l=r的时候就找到答案了。

我的写法是求 k k k大,所以把 k k k小转化成 k k k大就可以了,我偷了个懒,二分值域的时候直接分 [ 0 , 1 e 9 ] [0,1e9] [0,1e9]是会 T T T的(但是加了 O 2 O2 O2能过),读者对自己要求高的话建议写一下离散化或者二分出现了的值哦。

#include <cstdio>
const int M = 320005;
int read()
{
 int x=0,flag=1;char c;
 while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
 while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
 return x*flag;
}
int n,m,k,ans[M],bit[505][505];
struct node
{
    int x1,y1,x2,y2,v,id;
}a[M],L[M],R[M];
int lowbit(int x)
{
    return x&(-x);
}
void upd(int x,int y,int f)
{
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=n;j+=lowbit(j))
            bit[i][j]+=f;
}
int ask(int x,int y)
{
    int r=0;
    for(int i=x;i>=1;i-=lowbit(i))
        for(int j=y;j>=1;j-=lowbit(j))
            r+=bit[i][j];
    return r;
}
int get(int x1,int y1,int x2,int y2)
{
    return ask(x2,y2)+ask(x1-1,y1-1)-ask(x2,y1-1)-ask(x1-1,y2);
}
void cdq(int l,int r,int ql,int qr)
{
    if(qr<ql || l>r) return ;
    if(l==r)
    {
        for(int i=ql;i<=qr;i++) ans[a[i].id]=l;
        return ;
    }
    int mid=(l+r)>>1,tl=0,tr=0;
    for(int i=ql;i<=qr;i++)
    {
        if(a[i].id==0)
        {
            if(a[i].v>mid) upd(a[i].x1,a[i].y1,1),R[++tr]=a[i];
            else L[++tl]=a[i];
        }
        else
        {
            int t=get(a[i].x1,a[i].y1,a[i].x2,a[i].y2);
            if(a[i].v>t) a[i].v-=t,L[++tl]=a[i];
            else R[++tr]=a[i];
        }
    }
    for(int i=ql;i<=qr;i++)
        if(a[i].id==0 && a[i].v>mid)
            upd(a[i].x1,a[i].y1,-1);
    for(int i=1;i<=tl;i++) a[ql+i-1]=L[i];
    for(int i=1;i<=tr;i++) a[ql+tl+i-1]=R[i];
    cdq(l,mid,ql,ql+tl-1);
    cdq(mid+1,r,ql+tl,qr);
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            a[++k]=node{i,j,0,0,read(),0};
    for(int i=1;i<=m;i++)
    {
        int x1=read(),y1=read(),x2=read(),y2=read(),v=read();
        v=(x2-x1+1)*(y2-y1+1)-v+1;
        a[++k]=node{x1,y1,x2,y2,v,i};
    }
    cdq(0,1e9,1,k);
    //这里是需要卡常的哟,作者偷懒了(但是O2能过
    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值