Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)

Extending Set of Points

我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和。

然后就变成了一个并查集的问题, 但是这个题目里面有撤销的操作, 所以我们要把加入和撤销操作变成

这个点影响(L , R)之间的询问, 然后把它丢到线段树里面分成log段, 然后我们dfs一遍线段树, 用按秩合并
并查集取维护, 回溯的时候将并查集撤销。

#include<bits/stdc++.h>
#define LL long long
#define fi first
#define se second
#define mk make_pair
#define PLL pair<LL, LL>
#define PLI pair<LL, int>
#define PII pair<int, int>
#define SZ(x) ((int)x.size())
#define ull unsigned long long

using namespace std;

const int N = 3e5 + 7;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-6;
const double PI = acos(-1);

int n;
LL ans[N];
map<PII, int> Map;

int fa[N << 1], cntx[N << 1], cnty[N << 1], sz[N << 1];
LL now = 0;

#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
vector<PII> vc[N << 2];
void Insert(int L, int R, PII e, int l, int r, int rt) {
    if(l >= L && r <= R) {
        vc[rt].push_back(e);
        return;
    }
    int mid = l + r >> 1;
    if(L <= mid) Insert(L, R, e, lson);
    if(R >  mid) Insert(L, R, e, rson);
}

int getRoot(int x) {
    return fa[x] == x ? x : getRoot(fa[x]);
}

void Merge(int x, int y, stack<PII>& stk) {
    int fax = getRoot(x);
    int fay = getRoot(y);
    if(fax != fay) {
        if(sz[fax] < sz[fay]) swap(fax, fay);
        stk.push(mk(fax, fay));
        now -= 1ll * cntx[fax] * cnty[fax];
        now -= 1ll * cntx[fay] * cnty[fay];
        sz[fax] += sz[fay];
        cntx[fax] += cntx[fay];
        cnty[fax] += cnty[fay];
        fa[fay] = fax;
        now += 1ll * cntx[fax] * cnty[fax];
    }
}

void Delete(stack<PII>& stk) {
    while(!stk.empty()) {
        int fax = stk.top().fi;
        int fay = stk.top().se;
        stk.pop();
        now -= 1ll * cntx[fax] * cnty[fax];
        sz[fax] -= sz[fay];
        cntx[fax] -= cntx[fay];
        cnty[fax] -= cnty[fay];
        fa[fay] = fay;
        now += 1ll * cntx[fax] * cnty[fax];
        now += 1ll * cntx[fay] * cnty[fay];
    }
}

void dfs(int l, int r, int rt) {
    stack<PII> stk;
    for(auto& t : vc[rt]) Merge(t.fi, t.se, stk);
    if(l == r) ans[l] = now;
    else {
        int mid = l + r >> 1;
        dfs(l, mid, rt << 1);
        dfs(mid + 1, r, rt << 1 | 1);
    }
    Delete(stk);
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        PII e; scanf("%d%d", &e.fi, &e.se);
        e.se += 300000;
        if(Map.find(e) == Map.end()) {
            Map[e] = i;
        } else {
            Insert(Map[e], i - 1, e, 1, n, 1);
            Map.erase(e);
        }
    }
    for(auto& t : Map) Insert(t.se, n, t.fi, 1, n, 1);
    for(int i = 1; i <= 300000; i++) fa[i] = i, cntx[i] = 1, sz[i] = 1;
    for(int i = 300001; i <= 600000; i++) fa[i] = i, cnty[i] = 1, sz[i] = 1;
    dfs(1, n, 1);
    for(int i = 1; i <= n; i++) printf("%lld ", ans[i]);
    puts("");
    return 0;
 }

/*
*/

原文地址:https://www.cnblogs.com/CJLHY/p/10606629.html

时间: 2024-10-07 14:47:45

Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)的相关文章

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

Codeforces 19D Points 线段树+set

题目链接:点击打开链接 线段树维护y值大于val的最小x值 #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <iostream> #include <map> #include <set> #include <math.h> using namespace std; #define inf

codeforces 444 C. DZY Loves Colors(线段树)

题目大意: 1 l r x操作 讲 [l,r]上的节点涂成x颜色,并且每个节点的值都加上 |y-x| y为涂之前的颜色 2 l r  操作,求出[l,r]上的和. 思路分析: 如果一个区间为相同的颜色.那么我们才可以合并操作. 所以我们之前找相同的区间就好. 但是问题是如何合并操作. 那么我们定义一个val  表示这个区间每个位置上应该加上的值. pushdown 的时候这个值是可以相加的. #include <cstdio> #include <iostream> #includ

codeforces 446C DZY Loves Fibonacci Numbers 线段树

假如F[1] = a, F[2] = B, F[n] = F[n - 1] + F[n - 2]. 写成矩阵表示形式可以很快发现F[n] = f[n - 1] * b + f[n - 2] * a. f[n] 是斐波那契数列 也就是我们如果知道一段区间的前两个数增加了多少,可以很快计算出这段区间的第k个数增加了多少 通过简单的公式叠加也能求和 F[n]  = f[n - 1] * b + f[n - 2] * a F[n - 1] = f[n - 2] * b + f[n - 3] * a ..

Codeforces 444C DZY Loves Colors 水线段树

题目链接:点击打开链接 水.. #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <math.h> #include <set> #include <vector> #include <map> using namespace std; #define ll long long #defi

CodeForces - 383C Propagating tree(dfs + 线段树)

题目大意: 给出一棵树,树上每个节点都有权值,然后有两个操作. 1 x val 在结点x上加上一个值val,x的儿子加上 -val,x的儿子的儿子加上 - (-val),以此类推. 2 x 问x节点的值. 思路分析: 每个节点上加值都是给自己的儿子节点加,而且这个是颗树. 比如样例上的,如果你给node 1加一个值,那么五个节点都加. 再给node 2加个值,2的儿子节点也加了,之前给1加的值也要加到2号节点的儿子. 所以你会发现节点的儿子会存在一个从属的关系. 这样的话,我们可以把所有节点从新

codeforces Beta Round #19 D. Point (线段树 + set)

题目大意: 对平面上的点进行操作. add x y 在 (x,y )上加一个点. remove x y 移除 (x,y)上的点. find x y 求出在(x,y)右上角离他最近的点,优先级是靠左,靠下. 思路分析: find 操作 比较麻烦. 要保证x大的同时还要确保x最小,而且该x上还要有点. 这样要找大的时候要小的,就是在线段树上选择性的进入左子树还是右子树. 所以核心就是,用set维护叶子节点. 然后查找的时候去叶子节点找,如果这个叶子节点有蛮子的 x y  就输出,否则回溯去另外一个子

Nastya Hasn&#39;t Written a Legend(Codeforces Round #546 (Div. 2)E+线段树)

题目链接 传送门 题面 题意 给你一个\(a\)数组和一个\(k\)数组,进行\(q\)次操作,操作分为两种: 将\(a_i\)增加\(x\),此时如果\(a_{i+1}<a_i+k_i\),那么就将\(a_{i+1}\)变成\(a_i+k_i\),如果\(a_{i+2}<a_i+k_i\),则将\(a_{i+2}\)变成\(a_{i+1}+k_{i+1}\),以此类推. 查询\(\sum\limits_{i=l}^{r}a_i\). 思路 我们首先存下\(k\)数组的前缀和\(sum1\),

HDU 1540 &amp;&amp; POJ 2892 Tunnel Warfare (线段树,区间合并).

~~~~ 第一次遇到线段树合并的题,又被律爷教做人.TAT. ~~~~ 线段树的题意都很好理解吧.. 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1540 http://poj.org/problem?id=2892 ~~~~ 我的代码:200ms #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #defin