zoukankan      html  css  js  c++  java
  • 【线段树分治】【P5227】 [AHOI2013]连通图

    Description

    给定一个无向连通图和若干个小集合,每个小集合包含一些边,对于每个集合,你需要确定将集合中的边删掉后改图是否保持联通。集合间的询问相互独立

    定义一个图为联通的当且仅当对于任意的两个顶点,都存在一条路径连接它们

    Input

    第一行为两个整数 (n,m),代表无向图的点数和边数

    下面 (m) 行,包含两个整数 (u,v),代表该边连接点 (u,v)。第 (i + 1) 行的边的编号为 (i)。保证不存在重边和自环

    下面一行包含一个整数 (k),表示集合个数

    下面 (k) 行每行描述一个集合,每行的第一个数为集合中边的个数 (c),后面 (c) 个数代表集合内的边

    Output

    对于每个集合,输出一行代表去掉该集合中的边后图是否联通,如果联通输出 Connected,否则输出 Disconnected

    Hint

    (1~leq~n,k~leq~10^5)

    (1~leq~m~leq~2~ imes~10^5)

    (1~leq~c~leq~4)

    Solution

    这个分治方法叫做线段树分治,而不是cdq分治。

    我们考虑分治,设 ([l,r]) 为区间 ([l,r]) 中询问涉及边都没有被连上且其它边都连上时图是否联通,那么递归到底时即为一次询问的答案。

    具体方法为先将没有被询问过的边连上。然后在递归区间 ([l,r]) 时,将区间内 ([l,mid]) 的询问中不涉及的边全部连上,递归处理左侧区间,然后撤销上次的连边,再将 ((mid, r]) 中询问不涉及的边都连上,处理右边,回溯后再次撤销。在这个过程中用并查集维护一下联通块个数,在底部即可判断。存储需要撤销的操作可以用一个栈实现。

    考虑时间复杂度:递归层数为 (O(log k)),于是每个询问被处理 (O(log k)) 次,对每个询问中的每条边,每被处理一次就会有一次并查集的删除和撤销操作,由于是按秩合并实现,单次操作复杂度 (O(log n))。所以单个询问的复杂度为 (O(c~log n))。共有 (k) 个操作,于是总的时间复杂度为 (O(k~ imes~c~ imes~log k~log n))

    这个分治和cdq分治的区别大概在于……此分治时 mid 两侧的区间是被 “平等对待” 的,即记录右边区间对左边区间的贡献,但是cdq分治不是。

    另外上面做线段树分治的时候并没有将树显式的建出来,但他们在本质上是一样的。

    另外还有一种神仙做法在这里

    Code

    #include <cstdio>
    #include <vector>
    #ifdef ONLINE_JUDGE
    #define freopen(a, b, c)
    #endif
    
    typedef long long int ll;
    
    namespace IPT {
    	const int L = 1000000;
    	char buf[L], *front=buf, *end=buf;
    	char GetChar() {
    		if (front == end) {
    			end = buf + fread(front = buf, 1, L, stdin);
    			if (front == end) return -1;
    		}
    		return *(front++);
    	}
    }
    
    template <typename T>
    inline void qr(T &x) {
    	char ch = IPT::GetChar(), lst = ' ';
    	while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
    	while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
    	if (lst == '-') x = -x;
    }
    
    namespace OPT {
    	char buf[120];
    }
    
    template <typename T>
    inline void qw(T x, const char aft, const bool pt) {
    	if (x < 0) {x = -x, putchar('-');}
    	int top=0;
    	do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
    	while (top) putchar(OPT::buf[top--]);
    	if (pt) putchar(aft);
    }
    
    const int maxn = 100010;
    const int maxm = 200010;
    
    struct Edge {
    	int from, to;
    };
    Edge edge[maxm];
    
    struct Ask {
    	int c;
    	int mu[5];
    };
    Ask ask[maxn];
    
    struct OP {
    	int a, b, p;
    };
    OP stack[maxm * 20];
    
    int n, m, k, cnt, top;
    int ufs[maxn], rk[maxn];
    bool vis[maxm];
    
    void reset();
    int find(int);
    void cont(int, bool);
    void unionn(int, int, bool);
    void work(int, int, std::vector<int>&);
    void divide(int, int, std::vector<int>&);
    
    int main() {
    	freopen("1.in", "r", stdin);
    	qr(n); qr(m); cnt = n;
    	for (int i = 1; i <= m; ++i) {
    		qr(edge[i].from); qr(edge[i].to);
    	}
    	qr(k);
    	for (int i = 1; i <= k; ++i) {
    		qr(ask[i].c);
    		for (int j = 1; j <= ask[i].c; ++j) {qr(ask[i].mu[j]); vis[ask[i].mu[j]] = true;}
    	}
    	for (int i = 1; i <= n; ++i) ufs[i] = i, rk[i] = 1;
    	std::vector<int>dn;
    	for (int i = 1; i <= m; ++i) 
    		if (!vis[i]) cont(i, false);
    		else dn.push_back(i);
    	work(1, k, dn);
    	return 0;
    }
    
    void work(int l, int r, std::vector<int>&e) {
    	for (int i = l; i <= r; ++i) {
    		for (int j = 1; j <= ask[i].c; ++j) vis[ask[i].mu[j]] = true;
    	}
    	std::vector<int>dn;
    	int _tp = top;
    	for (int i = 0, len = e.size(); i < len; ++i) {
    		if (!vis[i]) cont(i, true);
    		else dn.push_back(i);
    	}
    	for (int i = l; i <= r; ++i) {
    		for (int j = 1; j <= ask[i].c; ++j) vis[ask[i].mu[j]] = false;
    	}
    	divide(l, r, dn);
    	while (top != _tp) reset();
    }
    
    void divide(int l, int r, std::vector<int>&e) {
    	if (l == r) {
    		puts(cnt == 1 ? "Connected" : "Disconnected");
    		return;
    	}
    	int mid = (l + r) >> 1;
    	work(l, mid, e);
    	work(mid + 1, r, e);
    }
    
    int find(int x) {return ufs[x] == x ? x : find(ufs[x]);}
    
    void cont(int x, bool rec) {
    	int fa = find(edge[x].from), fb = find(edge[x].to);
    	if (fa != fb) {
    		unionn(fa, fb, rec);
    		--cnt;
    	}
    }
    
    void unionn(int a, int b, bool rec) {
    	int c;
    	if (rk[a] < rk[b]) {c = 1; ufs[a] = b;}
    	else if (rk[a] > rk[b]) {c = 2; ufs[b] = a;}
    	else {c = 3; ufs[b] = a; ++rk[a];}
    	if (rec) stack[++top] = {a, b, c};
    }
    
    void reset() {
    	int a = stack[top].a, b = stack[top].b, p = stack[top--].p;
    	switch (p) {
    		case 1:
    			ufs[a] = a; break;
    		case 2:
    			ufs[b] = b; break;
    		case 3:
    			ufs[b] = b; --rk[a]; break;
    	}
    	++cnt;
    }
    
  • 相关阅读:
    linux下利用elk+redis 搭建日志分析平台教程
    C# 短信发送 邮件发送
    面向对象编程思想-装饰模式
    面向对象编程思想-桥接模式
    面向对象编程思想-适配器模式
    面向对象编程思想-原型模式
    面向对象编程思想-建造者模式
    面向对象编程思想-抽象工厂模式
    面向对象编程思想-工厂方法模式
    面向对象编程思想-简单工厂模式
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/10425529.html
Copyright © 2011-2022 走看看