「JSOI2014」矩形并

2021-04-18 08:28

阅读:676

标签:实现   扫描   扫描线   out   algo   线段   个数   efi   cst   

「JSOI2014」矩形并

传送门

我们首先考虑怎么算这个期望比较好。

我们不难发现每一个矩形要和 \(n - 1\) 个矩形去交,而总共又有 \(n\) 个矩形,所以我们把矩形两两之间的交全部加起来再除以 \(n(n - 1)\) 就是答案。

至于算矩形之间的交我们可以考虑把每个矩形都视为在这个矩形范围内区间加上 \(1\) ,那么我们只需要查询一个矩形内的和 - 该矩形自身的贡献就可以算出一个矩形与其他矩形的交。

所以现在相当于我们只需要实现二维的区间加/查询。

但是数据范围很大我们不可能用二维树状数组搞 不然这题就被爆艹了

所以我们考虑扫描线 + 一维树状数组来搞。

为什么不用线段树?这题线段树会不好搞,下面会讲到。

我们把纵坐标用树状数组维护起来,横坐标用扫描线代替。

对于每个矩形弄四条扫描线,两条修改两条查询。

其中一条修改在左边界加,另一条在右边界右边一个位置减。

其中一个查询在左边界左边一个位置算负的贡献,另一个在右边界算正的贡献。

那么有一个问题,为什么我们要对于左边界左边搞一条扫描线算负贡献呢?

我们考虑差分时的效果,我们对于一条加的修改扫描线,我们是把这条扫描线以左的一整块矩形区域都加了 \(1\)

那么我们如果在加之前,把一段区间的贡献减掉,实际上是减掉了若干个两个不同矩形左边界之间的值,那么我们这时如果在右边碰到一条查询正贡献的扫描线,我们会发现这两条扫描线的贡献一结合刚好就得到了一块矩形交的贡献,也就是说我们这样算就可以很方便地算矩形之间的交了。那么我们之所以不好用线段树就是因为我们这若干个两个不同矩形左边界之间的值的个数是 \(O(n)\) 的,所以不好搞。

具体细节可以手画一下图,那么我们接下来就用二维树状数组的实现技巧来搞。

还有一些小问题:运算过程中可能会爆 long long 所以我们用 long double 来存;然后就是树状数组不能以零为下标,所以我们再输入时把所有矩形的横纵坐标都往各正方向平移 \(2\) 个单位。

参考代码:

#include 
#include 
#define rg register
#define file(x) freopen(x".in", "r", stdin), freopen(x".out", "w", stdout)
using namespace std;
template  inline void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while ('0' > c || c > '9') f |= c == '-', c = getchar();
    while ('0' = 1; i -= i & -i) res += tr[i]; return res; }
} tr1, tr2, tr3, tr4;
 
inline void Update(int x, int y, int v) {
    tr1.update(y, v), tr2.update(y, x * v), tr3.update(y, y * v), tr4.update(y, (LL) x * y * v);
}
 
inline LL Query(int x, int y) {
    return tr1.query(y) * (x + 1) * (y + 1) - tr2.query(y) * (y + 1) - tr3.query(y) * (x + 1) + tr4.query(y);
}
 
int main() {
#ifndef ONLINE_JUDGE
    file("cpp");
#endif
    read(n);
    for (rg int x, y, a, b, i = 1; i 

「JSOI2014」矩形并

标签:实现   扫描   扫描线   out   algo   线段   个数   efi   cst   

原文地址:https://www.cnblogs.com/zsbzsb/p/12283494.html


评论


亲,登录后才可以留言!