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; 对于其他正常情况,我们对于每个颜色找到一个最小的矩形(这个矩形内 ...
随机推荐
- MYSQL如何优化?
MYSQL如何优化?结合你的经验 1.数据库的设计尽量把数据库设计的更小的占磁盘空间.1).尽可能使用更小的整数类型.(mediumint就比int更合适).2).尽可能的定义字段为not null, ...
- jstat详解
jstat 1. jstat -gc pid 可以显示gc的信息,查看gc的次数,及时间. 其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时 ...
- openwrt增加密码及ssh的方法
openwrt增加密码及ssh的方法 1.进入openwrt系统源码的顶层目录,然后执行 make menuconfig命令进入 Network--> SSH--> <*>o ...
- oracle查看数据库版本和字符集
以下以oralce为例, 查看数据库版本? 可以在pl/sql上执行:select * from v$version; 查看字符集? select * from v$nls_parameters; s ...
- 【unp】unix网络编程卷1-->环境搭建(ubuntu14.04)
学习unp网络编程,树上的例子均存在#include "unp.h",故需要对环境进行配置. 1. 到资源页下载unpv13e 2. 解压并将unpv13e 移动到相应的文件夹下 ...
- hdu 5279 YJC plays Minecraft——生成函数
题目:http://acm.hdu.edu.cn/showproblem.php?pid=5279 令 n 个点的树的 EGF 是 g(x) ,则 \( g(x) = \sum\limits_{i=0 ...
- Xcode 5 下的单元测试
新版Xcode 5和Server发布以后,apple对单元测试的支持是越来越好了.从这一点看出apple对单元测试的也是越来越重视了. 这篇Blog就简单的介绍这集成化测试功能. Server更新后是 ...
- API参考文档
Android 中文版:http://www.apiref.com/android-zh/index.html Bootstrap3 教程:http://www.apiref.com/bootstra ...
- sql格式化工具推荐
还在为上百行甚至上千行冗余的sql烦恼吗?这里推荐一款在线美化sql的工具 工具地址:http://www.matools.com/sql 怎么样,黑屏界面是不是带有满满的黑科技感? 该工具支持ora ...
- 汇编学习(1)——win7 64位调出debug
一.安装方法: 1.下载一个dosbox和win7 32位debug.exe,安装dosbox,打开页面 2. 将debug.exe放入磁盘根目录,这里以D盘为例.在dosbox中输入mount ...