[USACO22FEB] Paint by Rectangles P 题解
前言
没用线段树的小常数、小短码。
题目链接:洛谷。
题意简述
给出 \(n\) 个平行于坐标轴的矩形,各边所在直线互不重合,钦定最外面为白色,对这个平面图黑白染色,分别求黑色块数和白色块数。
题目分析
这道题扫描线一眼题吧?所以考虑从左到右扫描线。初始白色有 \(1\) 块。
加边
先考虑加边。加入一条竖边 \([l, r]\),就把这段区间异或一下,统计新增的白色块数和黑色块数。我们需要知道这条边对应的区间有多少个块。由于这些连通块是黑白相间的,我们只需要确定下面开始第一块的颜色是什么就行了。
记有 \(cnt\) 个块,黑色为 \(1\),白色为 \(0\),记最下面一块颜色是 \(c \in \lbrace 0, 1 \rbrace\)。
那么就让 \(c\) 的块数加上 \(\left \lceil \cfrac{cnt}{2} \right \rceil\);让 \(c \operatorname{xor} 1\) 的块数加上 \(\left \lfloor \cfrac{cnt}{2} \right \rfloor\)。
那么如何知道块数和最下面的颜色呢?使用树状数组维护即可。
我们维护横边。加边的时候用树状数组将 \(l, r\) 分别单点加一。那么块数就是 \([l + 1, r - 1]\) 中横边个数加一;最下方的颜色就查询 \(l\) 以下有多少条横边,若是奇数个,则是白色,反之是黑色。
这么说也许不清晰,来看看样例:
加入这条绿色的竖边。块数 \(cnt = 3\),由于 \(l\) 下方横边数量 \(0\) 是偶数,所以最下方颜色是黑色。就上黑色块数加上 \(\left \lceil \cfrac{3}{2} \right \rceil = 2\),白色块数加上 \(\left \lfloor \cfrac{3}{2} \right \rfloor = 1\)。
再来看看这条绿边。块数 \(cnt = 2\),由于 \(l\) 下方横边数量 \(3\) 是奇数,所以最下方颜色是白色。就上黑色块数加上 \(\left \lceil \cfrac{2}{2} \right \rceil = 1\),白色块数加上 \(\left \lfloor \cfrac{2}{2} \right \rfloor = 1\)。
删边
删边也同理,但是略有不同。记删边后,这里有 \(cnt\) 个块,最下面一块颜色是 \(c \in \lbrace 0, 1 \rbrace\)。
注意,如果此时 \(cnt = 1\),则表示这是一条结束的边。之前我们将 \(c\) 统计成多个块,但这时候它们到一起去了,所以要将 \(c\) 的块数减一。
其他情况注意最下面的颜色是和加边相反的,以及最上面的块和最下面的块是和外面的连成一个块,不做统计。
看看样例:
删去这条绿色的边。\(l\) 下面横边数量 \(1\) 是奇数,所以最下方颜色是黑色(而不是白色)。块数 \(cnt = 3\),但是最上面的黑色块属于橙色矩形,最下面的黑色块属于黄色矩形,这些块之前已经统计过了。所以让黑色块数加上 \(\left \lfloor \cfrac{cnt - 2}{2} \right \rfloor = 0\),白色块数加上 \(\left \lceil \cfrac{cnt - 2}{2} \right \rceil = 1\)。
删去这条绿色的边。\(l\) 下面横边数量 \(0\) 是偶数,所以最下方颜色是白色。块数 \(cnt = 1\),这就是上文提到的情况:在两个紫色圆圈处,我们将它统计成两个白色部分,而实际上它是一个块,所以将白色块数减一。
处理
这样就结束了吗?不不不,你可以看看下面的最简单的反例。
没错,只有一个矩形。我们会发现在删去右边的竖边时,不应该将白色块数减一。
如果这样的矩形不止一个,那么白色的块数就被少减了很多。
我们将白色块数加上矩形互相相交的连通块个数就行了,吗?但是还是不对,不光光只是外面的白色平面,我们考虑矩形完全包含的情况。
我们发现,处理里面红色矩形的时候,也会将黑色块数多减了一。此时,相当于红色矩形处在一个背景色是黑色的平面内,似乎是一个更小的子问题。
所以,我们在开始处理一个矩形互相相交的连通块时,首先看看它是处在一个黑色还是白色的平面内,将这个颜色数量加一,然后再处理这个子问题,这样就不会出现问题。
代码
时间复杂度 \(n (\log n + \alpha(n))\)。
具体注释看代码吧,略去了快读快写,目前是 Rank1。
// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <iostream>
#include <cstdio>
#define debug(a) cerr << "Line: " << __LINE__ << " " << #a << endl
#define print(a) cerr << #a << "=" << (a) << endl
#define file(a) freopen(#a".in", "r", stdin), freopen(#a".out", "w", stdout)
#define main Main(); signed main(){ return ios::sync_with_stdio(0), cin.tie(0), Main(); } signed Main
using namespace std;
#include <set>
int n, T;
int L[100010 << 1], R[100010 << 1];
// L[i] ~ R[i] 是横坐标为 i 的竖边
int toleft[100010 << 1], bl[100010 << 1], p[100010 << 1];
// toleft[i] 是将一个矩形右边的竖边映射到左边的竖边
// bl[i] 是一个左边的竖边所在矩形的编号
// p[i] 是一条横边所对应的左边的竖边编号
set<int> S;
// 用 set 维护目前出现的所有横边
struct Bit_Tree{
constexpr inline int lowbit(const int x){ return x & -x; }
int tree[100010 << 1];
void modify(int p, int v){ for (int i = p; i <= n; i += lowbit(i)) tree[i] += v; }
int query(int p){ int res = 0; for (int i = p; i; i -= lowbit(i)) res += tree[i]; return res; }
int query(int l, int r){ return r < l ? 0 : query(r) - query(l - 1); }
} yzh;
// yzh 是树状数组
int fa[100010];
bool vis[100010];
int get(int x){ return fa[x] == x ? x : fa[x] = get(fa[x]); }
void merge(int a, int b){ fa[get(a)] = get(b); }
// 并查集维护矩形连通性
signed main(){
read(n, T);
for (int i = 1, x1, y1, x2, y2; i <= n; ++i){
read(x1, y1, x2, y2);
L[x1] = L[x2] = y1, R[x1] = R[x2] = y2;
toleft[x2] = p[y1] = p[y2] = x1, bl[x1] = i, fa[i] = i;
}
n <<= 1;
for (int i = 1; i <= n; ++i){
if (!toleft[i]) S.insert(L[i]), S.insert(R[i]);
for (set<int>::iterator it = S.upper_bound(L[i]); it != S.end() && *it < R[i]; it = S.erase(it))
merge(bl[p[L[i]]], bl[p[*it]]);
if (toleft[i]) S.erase(L[i]), S.erase(R[i]);
// 发现只用在加入和删除竖边时,和已经存在的横边的矩形都有相交,并查集合并即可
}
long long ans[2] = {1, 0};
for (int i = 1; i <= n; ++i){
if (!toleft[i] && !vis[get(bl[i])]) vis[get(bl[i])] = true, ++ans[yzh.query(L[i]) & 1];
// 表明是这个连通块第一次出现,将背景色块数加一
if (!toleft[i]){ // 加边
int cnt = yzh.query(L[i] + 1, R[i] - 1) + 1; // 块数
if (yzh.query(L[i]) & 1){ // 最下面颜色是白色
ans[0] += (cnt + 1) >> 1, ans[1] += cnt >> 1;
} else {
ans[1] += (cnt + 1) >> 1, ans[0] += cnt >> 1;
}
yzh.modify(L[i], 1), yzh.modify(R[i], 1);
} else { // 删边
int cnt = yzh.query(L[i] + 1, R[i] - 1) + 1;
if (cnt == 1) --ans[(yzh.query(L[i]) & 1) ^ 1];
else if (yzh.query(L[i]) & 1){
ans[1] += (cnt - 2 + 1) >> 1, ans[0] += (cnt - 2) >> 1;
} else {
ans[0] += (cnt - 2 + 1) >> 1, ans[1] += (cnt - 2) >> 1;
}
yzh.modify(L[i], -1), yzh.modify(R[i], -1);
}
}
if (T == 1) write(ans[0] + ans[1]);
else write(ans[0], ' ', ans[1]);
return 0;
}
后记
比用欧拉公式,线段树加并查集的做法是不是好多了?
[USACO22FEB] Paint by Rectangles P 题解的更多相关文章
- ZOJ - 3781 Paint the Grid Reloaded 题解
题目大意: 给一个n*m的X O构成的格子,对一个点操作可以使与它相连通的所有一样颜色的格子翻转颜色(X—>O或O—>X),问给定的矩阵最少操作多少次可以全部变成一样的颜色. 思路: 1. ...
- AtCoder瞎做第二弹
ARC 067 F - Yakiniku Restaurants 题意 \(n\) 家饭店,\(m\) 张餐票,第 \(i\) 家和第 \(i+1\) 家饭店之间的距离是 \(A_i\) ,在第 \( ...
- [BZOJ 1260][CQOI2007]涂色paint 题解(区间DP)
[BZOJ 1260][CQOI2007]涂色paint Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为 ...
- 【题解】 bzoj1260: [CQOI2007]涂色paint (区间dp)
bzoj1260,懒得复制,戳我戳我 Solution: 这种题目我不会做qwq,太菜了 区间打牌(dp) 用f[l][r]表示从l到r最少需要染几次色. 状态转移方程: 1.\(f[l][r]=mi ...
- ZOJ 2747 Paint the Wall(离散化+暴力)题解
题意:给你一个面,然后涂颜色,问你最后剩多少颜色,每种颜色面积. 思路:第一反应是二维线段树,代码又臭又长,可以做.但是这题暴力+离散化就可以过.可以看到他给的n只有100,也就是说最坏情况下会涂10 ...
- HDU1510 White rectangles( 乱搞 O(n^3) )题解
思路: 友谊赛的时候一直想到了,但是没想出来怎么遍历才能找到所有矩阵,卡住了. 这里讲一下完整思路:我们用一个num[i][j]表示第i行第j列每一列连续的白色格子数量,然后我们定义一个MIN,并且每 ...
- [CQOI2007]涂色paint(BZOJ 1260)题解
题目描述 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续的木版涂成一个 ...
- 题解 P3117 【[USACO15JAN]牛的矩形Cow Rectangles】
暴力什么的就算了,贪心他不香吗 这题其实如果分开想,就三种情况需要讨论:(由于不会发图,只能手打) 1) 5 . . . . . 4 . . . . . 3 . . . H . 2 . . G . . ...
- LeetCode All in One题解汇总(持续更新中...)
突然很想刷刷题,LeetCode是一个不错的选择,忽略了输入输出,更好的突出了算法,省去了不少时间. dalao们发现了任何错误,或是代码无法通过,或是有更好的解法,或是有任何疑问和建议的话,可以在对 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
随机推荐
- Java类加载和对象创建
引言 Java代码需要被使用,必须要经过类加载器加载到内存中,然后对应的类才能够被创建使用,这文对类加载和对象创建和过程进行分析. 类加载 Java类通过懒加载的方式,经过了Loading.Linki ...
- CPU的一、二、三级缓存的区别
引言 概念 缓存大小也是CPU的重要指标之一,而且缓存的结构和大小对CPU速度的影响非常大,CPU内缓存的运行频率极高,一般是和处理器同频 运作,工作效率远远大于系统内存和硬盘.实际工作时,CPU往往 ...
- 雪花算法(SnowFlake)
引言 唯一ID可以标识数据的唯一性,在分布式系统中生成唯一ID的方案有很多,常见的方式大概有以下三种: 依赖数据库,使用如MySQL自增列或Oracle序列等. UUID随机数 snowflake雪花 ...
- sqlUtil
package com.cmbchina.monitor.utils;import com.alibaba.druid.sql.ast.SQLStatement;import com.alibaba. ...
- RSA密码系统的特定密钥泄露攻击与Coppersmith方法的应用
PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全.密码学.联邦学习.同态加密等隐私计算领域的技术和内容. RSA密码系统作为当前最广泛使用的公钥加密算法之一,其安全性依赖 ...
- 【干货】Vue3 组件通信方式详解
前言 毫无疑问,组件通信是Vue中非常重要的技术之一,它的出现能够使我们非常方便的在不同组件之间进行数据的传递,以达到数据交互的效果.所以,学习组件通信技术是非常有必要的,本文将总结Vue中关于组件通 ...
- hive第一课:# hive-3.1.2分布式搭建文档
hive-3.1.2分布式搭建文档 谷歌浏览器下载网址:Google Chrome – Download the fast, secure browser from Google 华为云镜像站:htt ...
- AT24C02、04、08、16 操作说明
我们这里介绍一下常见的EEPROM,ATMEL的AT24x系列中的AT24C02,学会了这个芯片,其他系列的芯片也是类似的. AT24C02的存储容量为2K bit,内容分成32页,每页8Byte ( ...
- Xilinx SDK 开发Linux APP
Xilinx SDK 开发Linux APP 步骤 配置环境变量 将工具链需要的程序的所在目录添加到 系统环境变量中,例如: D:\Xilinx_201803\SDK\2018.3\gnu\micro ...
- SpringBoot学习备忘
在 mapper.xml 中的 like 的写法 db1.name like "%"#{name}"%" 参考mybatis mapper.xml中like的写 ...