Gym 102028J 扫描线/二维差分 + 解方程
题意:有一个二维平面,以及n个操作,每个操作会选择一个矩形,使得这个二维平面的一部分被覆盖。现在你可以取消其中的2个操作,问最少有多少块地方会被覆盖?
思路:官方题解简洁明了,就不细说了:https://codeforces.com/blog/entry/63729。
此处重点记录一下两种做法的巧妙之处。
1:二维差分+解方程
二维差分:假设在矩形[(x1, y1), (x2, y2)]上加一个数,那么在(x1, y1), (x2 + 1, y2 + 1)加1, (x1, y2 + 1), (x2 +1, y1)减1。扫描的时候,cnt[i][j] += cnt[i - 1][j], cnt[i][j] += cnt[i][j - 1], cnt[i][j] -= cnt[i - 1][j - 1],这样线性扫描一遍就可以知道每个点被覆盖了多少次。
解方程:只知道一个点被覆盖多少次是没有用的,我们需要知道覆盖的具体方案。对于只被覆盖的一次的我们很容易知道。但是对于覆盖两次的呢?我们采用记录和 和 平方和的方式,这样通过解方程就知道这个位置的数是哪两个数了。
代码:
#include <bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f
#define db double
#define pii pair<int, int>
using namespace std;
const int maxn = 1510;
LL sqr[maxn][maxn], sum[maxn][maxn], cnt[maxn][maxn];
LL cnt0, num[300010];
void add(int x, int y, LL val, LL flag) {
cnt[x][y] += flag;
sum[x][y] += val * flag;
sqr[x][y] += val * val * flag;
}
void update(int x1, int y1, int x2, int y2, LL flag) {
cnt[x1][y1] += flag * cnt[x2][y2];
sum[x1][y1] += flag * sum[x2][y2];
sqr[x1][y1] += flag * sqr[x2][y2];
}
vector<pii> a;
int main() {
int T, n, m, x1, y1, x2, y2;
LL ans;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
a.clear(), ans = 0, cnt0 = 0;
for (int i = 1; i <= n; i++)
num[i] = 0;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j++)
sqr[i][j] = sum[i][j] = cnt[i][j] = 0;
}
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d", &x1, &x2, &y1, &y2);
add(x1, y1, i, 1), add(x2 + 1, y2 + 1, i, 1);
add(x1, y2 + 1, i, -1), add(x2 + 1, y1, i, -1);
}
for (int i = 1; i <= m; i++)
for (int j = 1; j <= m; j++) {
update(i, j, i - 1, j, 1);
update(i, j, i, j - 1, 1);
update(i, j, i - 1, j - 1, -1);
if(cnt[i][j] == 0) cnt0++;
else if(cnt[i][j] == 1) {
num[sum[i][j]]++;
} else if(cnt[i][j] == 2){
LL tmp = sum[i][j] * sum[i][j] - sqr[i][j];
LL x = (sum[i][j] + sqrt(sqr[i][j] - tmp)) / 2;
LL y = sum[i][j] - x;
if(x > y) swap(x, y);
a.push_back(make_pair(x, y));
}
}
LL res[3];
res[0] = res[1] = res[2] = 0;
for (int i = 1; i <= n; i++) {
res[0] = num[i];
sort(res, res + 3);
}
ans = max(ans, res[1] + res[2]);
sort(a.begin(), a.end());
for (int i = 0, j = 0; i < a.size(); i = j) {
while(j < a.size() && a[i] == a[j])j++;
ans = max(ans, num[a[i].first] + num[a[i].second] + (j - i));
}
printf("%lld\n", m * m - cnt0 - ans);
}
}
2:扫描线,我们通过线段树来执行扫描线来知道覆盖一次和覆盖两次的个数以及具体方案,扫描线的实现比较巧妙,有种懒标记下放和标记永久化结合的味道。具体是这样的,对于每个矩形[(x1, y1), (x2, y2)], 我们在y1位置记录(x1, x2)加上一个数,在y2 + 1位置记录删除这个数。每扫描到一个新的位置,我们把在这个位置记录的标记打上,以及记录上要删除上。之后,我们遍历线段树,我们记录3个数,这3个数是从上层可以下放的标记,这样到最底层的时候,如果下放的标记小于3个,就可以在对应位置记录了。如果当前从上层可以下放的标记已经到达3个了,直接return就行。
代码:
#include <bits/stdc++.h>
#define ls (o << 1)
#define rs (o << 1 | 1)
#define ed tr[o].size() - 1
#define pii pair<int, int>
using namespace std;
const int maxn = 300010;
const int maxm = 1510;
vector<int> tr[maxm << 2], re_num[maxm], del_re[maxm];
vector<pii> a, re[maxm];
int del[maxn], num[maxn], cnt0;
void build(int o, int l, int r) {
tr[o].clear();
if(l == r) {
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void add(int o, int l, int r, int ql, int qr, int val) {
if(l >= ql && r <= qr) {
tr[o].push_back(val);
return;
}
int mid = (l + r) >> 1;
if(ql <= mid) add(ls, l, mid, ql, qr, val);
if(qr > mid) add(rs, mid + 1, r, ql, qr, val);
}
void pushdown(int o, int& a1, int& a2, int& a3) {
int q[4];
memset(q, 0, sizeof(q));
while(tr[o].size() && q[0] < 3) {
if(del[tr[o][ed]] == 1) tr[o].pop_back();
else {
q[++q[0]] = tr[o][ed];
tr[o].pop_back();
}
}
for (int i = 1; i <= q[0]; i++) {
if(a1 == 0) a1 = q[i];
else if(a2 == 0) a2 = q[i];
else if(a3 == 0) a3 = q[i];
tr[o].push_back(q[i]);
}
}
void dfs(int o, int l, int r, int a1, int a2, int a3) {
if(a3 != 0) return;
pushdown(o, a1, a2, a3);
if(l == r) {
if(a1 == 0) cnt0++;
else if(a3 == 0) {
if(a2 == 0) num[a1]++;
else a.push_back(make_pair(min(a1, a2), max(a1, a2)));
}
return;
}
int mid = (l + r) >> 1;
dfs(ls, l, mid, a1, a2, a3);
dfs(rs, mid + 1, r, a1, a2, a3);
}
int main() {
int T, n, m, x1, x2, y1, y2;
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
build(1, 1, m);
cnt0 = 0;
a.clear();
for (int i = 1; i <= m + 1; i++) {
re_num[i].clear();
del_re[i].clear();
re[i].clear();
}
for (int i = 1; i <= n; i++) {
scanf("%d%d%d%d", &x1, &x2, &y1, &y2);
re[y1].push_back(make_pair(x1, x2));
re_num[y1].push_back(i);
del_re[y2 + 1].push_back(i);
del[i] = 0;
num[i] = 0;
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j < re[i].size(); j++) {
pii x = re[i][j];
add(1, 1, m, x.first, x.second, re_num[i][j]);
}
for (int j = 0; j < del_re[i].size(); j++) {
del[del_re[i][j]] = 1;
}
dfs(1, 1, m, 0, 0, 0);
}
int tmp[3];
memset(tmp, 0, sizeof(a));
for (int i = 1; i <= n; i++) {
tmp[0] = num[i];
sort(tmp, tmp + 3);
}
int ans = tmp[1] + tmp[2];
sort(a.begin(), a.end());
for (int i = 0, j = 0; i < a.size(); i = j) {
while(j < a.size() && a[i] == a[j]) j++;
ans = max(ans, num[a[i].first] + num[a[i].second] + j - i);
}
printf("%d\n", m * m - cnt0 - ans);
}
}
理论复杂度第一种比第二种略优秀,但是实际情况第二种比第一种快400ms,可能因为线段树的剪枝吧。
Gym 102028J 扫描线/二维差分 + 解方程的更多相关文章
- 【bzoj5173】[Jsoi2014]矩形并  扫描线+二维树状数组区间修改区间查询
		
题目描述 JYY有N个平面坐标系中的矩形.每一个矩形的底边都平行于X轴,侧边平行于Y轴.第i个矩形的左下角坐标为(Xi,Yi),底边长为Ai,侧边长为Bi.现在JYY打算从这N个矩形中,随机选出两个不 ...
 - 洛谷  P3397 地毯 【二维差分标记】
		
题目背景 此题约为NOIP提高组Day2T1难度. 题目描述 在n*n的格子上有m个地毯. 给出这些地毯的信息,问每个点被多少个地毯覆盖. 输入输出格式 输入格式: 第一行,两个正整数n.m.意义如题 ...
 - HDU - 6514 Monitor(二维差分)
		
题意 给定一个\(n×m\)的矩阵.(\(n×m <= 1e7\)). \(p\)次操作,每次可以在这个矩阵中覆盖一个矩形. \(q\)次询问,每次问一个矩形区域中,是否所有的点都被覆盖. 解析 ...
 - NOI 2012 魔幻棋盘 | 二维差分 + 二维线段树
		
题目:luogu 2086 二维线段树,按套路差分原矩阵,gcd( x1, x2, ……, xn ) = gcd( xi , x2 - x1 , ……, xn - xn-1 ),必须要有一个原数 xi ...
 - Codeforces 1262E Arson In Berland Forest(二维前缀和+二维差分+二分)
		
题意是需要求最大的扩散时间,最后输出的是一开始的火源点,那么我们比较容易想到的是二分找最大值,但是我们在这满足这样的点的时候可以发现,在当前扩散时间k下,以这个点为中心的(2k+1)2的正方形块内必 ...
 - Monitor HDU6514 二维差分入门学习
		
Monitor HDU 6514 二维差分入门学习 题意 小腾有\(n*m\)的田地,但是有小偷来偷东西,在一片矩形区域上,有一部分区域是监控可以覆盖到的,这部分区域由一个或多个包含于该矩形区域的小矩 ...
 - Codeforces Round #578 (Div. 2) 二维差分 可做模板
		
题意: 在n*n的矩阵中,你可以选择一个k*k的子矩阵,然后将这个子矩阵中的所有B全部变为W,问你怎么选择这个子矩阵使得最终的矩阵中某一行全是W或者某一列全是W的个数最多 题解:考虑每一行和每一列,对 ...
 - 2020ICPC·小米 网络选拔赛第一场  J.Matrix Subtraction  (贪心,二维差分)
		
题意:给一个\(nXm\)的矩阵,可以选取\(aXb\)的子矩阵,使子矩阵中的所有元素减一,问最后是否能使矩阵中所有元素变为\(0\). 题解:首先贪心,我们看最左上角的元素,如果\(g[1][1]\ ...
 - 220514 T2 画画 (二维差分)
		
首先我们需要特判只涂了一种颜色的情况: (1)k=1,此时答案就是1:(2)k>1,涂的这种颜色肯定不能是第一个,答案是k-1; 对于其他正常情况,我们对于每个颜色找到一个最小的矩形(这个矩形内 ...
 
随机推荐
- 工作中SQL语句的优化
			
在我们的工作中,数据是很多的,这是我常见问题遇到的问题做了简短总结. 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 w ...
 - RxJava总结(原)
			
1.RxJava的作用 RxJava is a Java VM implementation of Reactive Extensions: a library for composing async ...
 - react 编写 基于ant.design 页面的参考笔记
			
前言 因为我没有系统的学习 react,是边写边通过搜索引擎找相对的问题,看 ant.design的 中文文档 编写的一个单页面, 以下的笔记都是写 gksvideourlr 时记录的. 重新设定表单 ...
 - redis相关笔记(三.redis设计与实现(笔记))
			
redis笔记一 redis笔记二 redis笔记三 1.数据结构 1.1.简单动态字符串: 其属性有int len:长度,int free:空闲长度,char[] bur:字符数组(内容) 获取字符 ...
 - 最佳实践 | RDS & POLARDB归档到X-Pack Spark计算
			
X-Pack Spark服务通过外部计算资源的方式,为Redis.Cassandra.MongoDB.HBase.RDS存储服务提供复杂分析.流式处理及入库.机器学习的能力,从而更好的解决用户数据处理 ...
 - UNP学习第13章 守护进程和inetd超级服务器
			
Unix系统中的syslogd守护进程通常由某个系统初始化脚本启动,而且在系统工作期间一直运行. 源自Berkeley的syslogd实现在启动时执行以下步骤. (1)读取配置文件.通常为/etc/s ...
 - AcWing   215. 破译密码     (莫比乌斯反演)打卡
			
达达正在破解一段密码,他需要回答很多类似的问题: 对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d. 作为达达的同学,达达希望得到你的帮助. ...
 - mysql负载高分析
			
table_open_cache SHOW STATUS LIKE 'Open%tables'; SHOW global variables LIKE '%table%'; # 如果你发现 op ...
 - selenium IDE 安装环境配置
 - SqlSession(SqlSessionTemplate类) 实现Mybatis
			
yBatis3与spring整合之使用SqlSession(SqlSessionDaoTemplate类) ---------- 注:这是手工编写实现的方式(其实可以直接使用注入映射器的) SqlSe ...