Description

给定一个大小为 \(n\times m\) 的 \(01\) 矩阵。

要求支持:单点翻转,询问子矩形内部最大正方形。

\(n\times m\leq 4\cdot 10^6,n\leq m,q\leq 2000\)。

Sol

线段树神题。

我们来一步步解决问题。

首先考虑询问整个矩形,且只有一次询问怎么做。

我们可以 \(O(n)\) 的枚举矩形的上边界,再 \(O(m)\) 的枚举右边界,再安排一个指针表示矩形的左边界,这样可以发现,随着右边界增大,这个指针是单调不减的。那怎么判断当前枚举的正方形是否合法呢?根据这个单调不减的性质,我们可以用一个单调队列来维护这个左指针,具体就不细说了,大概就是每个点记录一个向下最长的一段 \(1\) 能够延伸多长就好了。

然后回到该问题。

观察到 \(q,m\) 比较小,所以可以让复杂度偏向 \(q,m\)。

对 \(n\) 这一维开一棵线段树,线段树上每个节点表示的矩形高度都为 \(m\)。

然后问题就是如何合并信息,即知道两个子区间的信息能否合并出大区间中跨越 \(mid\) 的最大子矩形。

所以我们除了在每个节点维护最大子矩形之外,还要维护两个信息 \(lmx[i],rmx[i]\) 表示第 \(i\) 行从左端点和右端点分别最长能延伸多长的一段 \(1\)。

然后就支持合并了,具体就是,枚举一个子矩形的下边界 \(i\),然后用两个单调队列维护左儿子的右边界和右儿子的左边界,再拿个指针维护子矩形的上边界,就可以均摊 \(O(m)\) 求出以每行为下边界跨越 \(mid\) 的最大子矩形了。

不想说了不想说了。

看代码吧。

Code

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
const int N=4e6+5;
#define ls x<<1
#define rs x<<1|1
#define lss ls,l,mid,ql,qr
#define rss rs,mid+1,r,ql,qr int q1[N],q2[N];
int n,m,q,ret,len;
int hd1,tail1,hd2,tail2; struct Node{
int f[N<<2];
int* operator[](int x){return f+x*m;}//这个重载中括号很巧很巧
}mp,lmx,rmx,sum; void pushup(int x,int l,int r){
int mid=l+r>>1,len1=mid-l+1,len2=r-mid; //[l,mid] [mid+1,r]
hd1=hd2=1;tail1=tail2=0;
for(int j=1,i=1;i<=m;i++){
while(hd1<=tail1 and rmx[ls][q1[tail1]]>=rmx[ls][i]) tail1--; q1[++tail1]=i;
while(hd2<=tail2 and lmx[rs][q2[tail2]]>=lmx[rs][i]) tail2--; q2[++tail2]=i;
while(hd1<=tail1 and hd2<=tail2 and i-j+1>rmx[ls][q1[hd1]]+lmx[rs][q2[hd2]]){
j++;
if(q1[hd1]<j) hd1++;
if(q2[hd2]<j) hd2++;
}
sum[x][i]=max(sum[ls][i],sum[rs][i]);
if(hd1<=tail1 and hd2<=tail2) sum[x][i]=max(sum[x][i],i-j+1);
}
for(int i=1;i<=m;i++)
lmx[x][i]=lmx[ls][i]+(lmx[ls][i]==len1?lmx[rs][i]:0),
rmx[x][i]=rmx[rs][i]+(rmx[rs][i]==len2?rmx[ls][i]:0);
} void build(int x,int l,int r){
if(l==r){
for(int i=1;i<=m;i++)
lmx[x][i]=rmx[x][i]=sum[x][i]=mp[l][i];
return;
} int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(x,l,r);
} void modify(int x,int l,int r,int ql,int qr,int c){
if(l==r){
lmx[x][c]^=1;rmx[x][c]=sum[x][c]=lmx[x][c];
return;
} int mid=l+r>>1;
ql<=mid?modify(lss,c):modify(rss,c);
pushup(x,l,r);
} void merge(int x,int l,int r,int QL,int QR){
hd1=hd2=1;tail1=tail2=0;
int len1=len,len2=r-l+1;
for(int j=QL,i=QL;i<=QR;i++){
while(hd1<=tail1 and rmx[0][q1[tail1]]>=rmx[0][i]) tail1--; q1[++tail1]=i;
while(hd2<=tail2 and lmx[x][q2[tail2]]>=lmx[x][i]) tail2--; q2[++tail2]=i;
while(hd1<=tail1 and hd2<=tail2 and i-j+1>rmx[0][q1[hd1]]+lmx[x][q2[hd2]]){
j++;
if(q1[hd1]<j) hd1++;
if(q2[hd2]<j) hd2++;
}
if(hd1<=tail1 and hd2<=tail2) ret=max(ret,i-j+1);
}
for(int i=QL;i<=QR;i++)
lmx[0][i]=lmx[0][i]+(lmx[0][i]==len1?lmx[x][i]:0),
rmx[0][i]=rmx[x][i]+(rmx[x][i]==len2?rmx[0][i]:0);
} void query(int x,int l,int r,int ql,int qr,int QL,int QR){
if(ql<=l and r<=qr){
for(int i=QL;i<=QR;i++)
ret=max(ret,min(sum[x][i],i-QL+1));
merge(x,l,r,QL,QR);
len+=r-l+1;
return;
} int mid=l+r>>1;
if(ql<=mid) query(lss,QL,QR);
if(mid<qr) query(rss,QL,QR);
} int query(int ql,int qr,int QL,int QR){
ret=len=0;
for(int i=1;i<=m;i++) lmx[0][i]=rmx[0][i]=0;
query(1,1,n,ql,qr,QL,QR);
return ret;
} signed main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&mp[i][j]);
build(1,1,n);
while(q--){
int opt; scanf("%d",&opt);
if(!opt){
int x,y; scanf("%d%d",&x,&y);
modify(1,1,n,x,x,y);
} else{
int l,s,r,t; scanf("%d%d%d%d",&l,&s,&r,&t);
printf("%d\n",query(l,r,s,t));
}
} return 0;
}

[Code+#3] 寻找车位的更多相关文章

  1. CODE[VS]-寻找子串位置-字符串处理-天梯青铜

    题目描述 Description 给出字符串a和字符串b,保证b是a的一个子串,请你输出b在a中第一次出现的位置. 输入描述 Input Description 仅一行包含两个字符串a和b 输出描述 ...

  2. loj #6302. 「CodePlus 2018 3 月赛」寻找车位【线段树+单调队列】

    考虑静态怎么做:枚举右边界,然后枚举上边界,对应的下边界一定单调不降,单调栈维护每一列从当前枚举的右边界向左最长空位的长度,这样是O(nm)的 注意到n>=m,所以m<=2000,可以枚举 ...

  3. [luogu4259]寻找车位

    考虑一个分治的做法:按行分治,将所有区间分为两类--经过分割线的.在左/右区间内部,后者显然可以递归下取,考虑前者 先求出出该行上每一列向上和向下的最大长度,记作$up_{i}$和$down_{i}$ ...

  4. <停车卫> 产品需求说明书 version 2.0

    <停车卫> 产品需求说明书 文档版本号: Version 2.0 文档编号: xxxx 文档密级: 归属部门/项目: 产品名: 停车卫 子系统名: 编写人: kina 编写日期: 2015 ...

  5. 瞬间读懂什么是互联网思维、大数据、O2O、众筹、红海

     1.什么叫大数据? 某必胜客店的电话铃响了,客服人员拿起电话. 客服:必胜客.您好,请问有什么需要我为您服务? 顾客:你好,我想要一份…… 客服:先生,烦请先把您的会员卡号告诉我. 顾客:16846 ...

  6. MySQL的诡异同步问题-重复执行一条relay-log

    MySQL的诡异同步问题 近期遇到一个诡异的MySQL同步问题,经过多方分析和定位后发现居然是由于备份引发的,非常的奇葩,特此记录一下整个问题的分析和定位过程. 现象 同事扩容的一台slave死活追不 ...

  7. 中文分词系列(一) 双数组Tire树(DART)详解

    1 双数组Tire树简介 双数组Tire树是Tire树的升级版,Tire取自英文Retrieval中的一部分,即检索树,又称作字典树或者键树.下面简单介绍一下Tire树. 1.1 Tire树 Trie ...

  8. 线下市场,选择微信小程序从未显得如此重要

    2017 年 1 月 9 日,小程序正式上线,到今日,3 月 8 号,这个新产品面世刚好满两个月.小程序刚推出便受到全球关注,腾讯股价当天即创逾一个月高位,但关注度先是急速上涨,不久便迅速降温,甚至在 ...

  9. Python(简单计算器)

    参考:https://www.cnblogs.com/alex3714/articles/5169958.html import re ret = re.search('\([^()]+\)','(1 ...

随机推荐

  1. 如何查看ubuntu系统版本信息

    第一种方法: hadoop@master:~$ cat /proc/version Linux version 4.4.0-21-generic (buildd@lgw01-21):Linux内核版本 ...

  2. System.data.sqlclient.sqlexception:将截断字符串或二进制数据终止

    System.data.sqlclient.sqlexception:将截断字符串或二进制数据终止. 错误原因:输入的字符串长度超过数据库设置的长度

  3. vue font-icon 图标

    1.vue 游览器左上角小图标 把.ico文件放在根目录下的static文件夹下,然后link标签引入 <link rel="shortcut icon" href=&quo ...

  4. webpack 4.0 中 clean-webpack-plugin 的使用

    其实 clean-webpack-plugin 很容易知道它的作用,就是来清除文件的. 一般这个插件是配合 webpack -p 这条命令来使用,就是说在为生产环境编译文件的时候,先把 build或d ...

  5. 脚本语言丨Batch入门教程第四章:调用与传参

    今天是Batch入门教程的最后一章内容:调用与传参.相信通过前面的学习,大家已经掌握了Windows Batch有关的基础知识和编程方法,以及利用Windows Batch建立初级的编程思维方式.今后 ...

  6. Android单元测试之四:仪器化测试

    Android单元测试之四:仪器化测试 仪器化测试 在某些情况下,虽然可以通过模拟的手段来隔离 Android 依赖,但代价很大,这种情况下可以考虑仪器化的单元测试,有助于减少编写和维护模拟代码所需的 ...

  7. [Swift]LeetCode951. 翻转等价二叉树 | Flip Equivalent Binary Trees

    For a binary tree T, we can define a flip operation as follows: choose any node, and swap the left a ...

  8. Python面试真题第二节

    26.字符串a = "not 404 found 张三 99 深圳",每个词中间是空格,用正则过滤掉英文和数字,最终输出"张三 深圳" 27.filter方法求 ...

  9. 【dotNet Core】Swagger下简单的给WebApi分组

    Startup.cs下ConfigureServices代码 这里主要在DocInclusionPredicate控制输出那些api. Startup.cs下Configure代码 给Controll ...

  10. admui框架使用经验

    刚开始接触admui框架时确实有些迷茫,不知道怎么使用,摸索了一段时间后才发现这个框架很简单!以下是我遇见的一些坑,总结一下啦! 1.使用框架第一步就是开启服务器,我给公司写项目时开启的是5000端口 ...