Ginger的操作序列I

  众所周知,\(Ginger\)是一位实力强劲的算法竞赛选手,擅长各种各样的算法,一切问题对于 \(Ginger\)来说都游刃有余。这一天\(Ginger\)发现了一个有趣的题目,题目的内容是这样的:
  有\(n\)个算法,\(Ginger\)每次选择一个区间\([l,r]\),学习这个区间里的所有算法,但是每个算法只能学习一次(因为\(Ginger\)一次就学会了),所以\(Ginger\)想知道每次他能新学会几种算法。这个问题实在太简单了\(Ginger\)微微一笑就做出了这道题,但是\(Ginger\)决定拿这道题考考你。
输入格式:
  第一行两个数 \(n,m (1 \leq n,m\leq 2×10^6)\)\(n\)代表\(n\)种算法,\(m\)代表\(Ginger\)的操作次数。接下来\(m\)行,每行两个数 \(l,r (1\leq l \leq r\leq n)\),代表\(Ginger\)学会 \([l,r]\) 这个区间的算法。

思路:
  看完题目可以知道,这道题只需要执行区间覆盖操作和区间查询操作,很自然的就可以想到珂朵莉树这么一个很好用的数据结构,但是交上去\(TLE\)了,再看看数据范围\(2 * 10^6\),也就是说如果卡的很死的话,单次区间查询的操作就会被卡成\(O(n)\)级别的,那么就需要去换一个线性的做法。
  因为这个题目里面只有\(0,1\)两种状态的集合,所以我们可以考虑用\(DSU\)将已经学过了的题目合并在一个集合里面,这样做就是线性了每个点只会被访问一次。在每一次询问,我们都让当前节点的父节点和他后一个节点的父节点合并为一个集合,这样当我们重新访问一段已经访问过的区间的时候,就可以直接跳到最近的一个没有访问过的节点。

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n, q;
    std::cin >> n >> q;

    DSU uf(n + 2);

    for (int i = 0; i < q; i ++ ) {
        int l, r;
        std::cin >> l >> r;
        int fl = uf.leader(l);
        int ans = 0;
        for (int j = fl; j <= r;) {
            ans ++;
            uf.f[j] = j + 1;
            j = uf.leader(j + 1);
        }
        std::cout << ans << "\n";
    }

    return 0 ^ 0;
}
posted @ 2022-08-12 11:33  浅渊  阅读(64)  评论(0)    收藏  举报