• BZOJ 3237 连通图(随机化+线性基)/ (分治+并查集)


    题目

    题目链接

    记得数据范围在HINT处

    题解1

    直接离线分治

    cdq(l,r)cdq(l,r)表示[l,r][l,r]范围内的询问中涉及到的所有边都不连且其它边都连。

    那么只要l=rl=r,就恰好是该询问的边断开,并查集判断即可。

    分治的时候要往[l,mid][l,mid]走,就先把在[mid+1,r][mid+1,r]且不在[l,mid][l,mid]的边加入。
    分治回来后撤销。

    然后要往[mid+1,r][mid+1,r]走,就把在[l,mid][l,mid]但不在[mid+1,r][mid+1,r]的边加入。
    回来后撤销。

    并查集撤销只需要存两个值,一个父亲一个儿子。

    O(nlog2n)O(nlog^2n)
    这能叫cdq?

    CODE

    5000ms

    #include <bits/stdc++.h>
    using namespace std;
    char cb[1<<18],*cs,*ct;
    #define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
    inline void rd(int &x) {
    	x = 0; char ch; while(!isdigit(ch=getchar()));
    	do x=x*10+ch-'0';while(isdigit(ch=getchar()));
    }
    const int MAXN = 100005;
    const int MAXM = 200005;
    int n, m, k, fa[MAXN], u[MAXM], v[MAXM], c[MAXN], e[MAXN][4], siz[MAXN];
    inline int find(int x) { while(fa[x] != x) x = fa[x]; return x; }
    int q[MAXN<<1], top;
    inline void uni(int x, int y) {
    	x = find(x), y = find(y);
    	if(x != y) {
    		if(siz[x] < siz[y]) swap(x, y); //siz[x] > siz[y]
    		q[++top] = x; //fa
    		q[++top] = y; //son
    		fa[y] = x; siz[x] += siz[y];
    	}
    }
    inline void cancel(int now) {
    	int x, y;
    	while(top > now) {
    		y = q[top--];
    		x = q[top--];
    		siz[x] -= siz[y];
    		fa[y] = y;
    	}
    }
    bool ans[MAXN], vis[MAXM];
    void cdq(int l, int r) {
    	if(l == r) {
    		ans[l] = siz[find(1)] == n;
    		return;
    	}
    	int mid = (l + r) >> 1, now = top;
    	for(int i = l; i <= mid; ++i) for(int j = 0; j < c[i]; ++j) vis[e[i][j]] = 1;
    	for(int i = mid+1; i <= r; ++i)
    		for(int j = 0; j < c[i]; ++j)
    			if(!vis[e[i][j]]) uni(u[e[i][j]], v[e[i][j]]);
    	for(int i = l; i <= mid; ++i) for(int j = 0; j < c[i]; ++j) vis[e[i][j]] = 0;
    	cdq(l, mid); cancel(now);
    
    	for(int i = mid+1; i <= r; ++i) for(int j = 0; j < c[i]; ++j) vis[e[i][j]] = 1;
    	for(int i = l; i <= mid; ++i)
    		for(int j = 0; j < c[i]; ++j)
    			if(!vis[e[i][j]]) uni(u[e[i][j]], v[e[i][j]]);
    	for(int i = mid+1; i <= r; ++i) for(int j = 0; j < c[i]; ++j) vis[e[i][j]] = 0;
    	cdq(mid+1, r); cancel(now);
    }
    int main () {
    	rd(n), rd(m);
    	for(int i = 1; i <= m; ++i) rd(u[i]), rd(v[i]);
    	for(int i = 1; i <= n; ++i) fa[i] = i, siz[i] = 1;
    	rd(k);
    	for(int i = 1; i <= k; ++i) { rd(c[i]); for(int j = 0; j < c[i]; ++j)rd(e[i][j]), vis[e[i][j]] = 1; }
    	for(int i = 1; i <= m; ++i) if(!vis[i]) uni(u[i], v[i]);
    	memset(vis, 0, sizeof vis);
    	cdq(1, k);
    	for(int i = 1; i <= k; ++i) puts(ans[i] ? "Connected" : "Disconnected");
    }
    

    题解2

    分治的做法但是很慢

    这里给出随机化+线性基的做法。

    先找一棵生成树,然后边被分为了树边和非树边。

    首先给每条非树边一个随机的权值。

    然后如果整棵树在EE处断开,一定是链接左右两个连通块的非树边和EE均被删除。

    那么我们定义树边的权值为链接左右两个连通块的非树边的权值异或和,如果能预处理出这个值,就可以通过看是否有边集的子集异或和为0来判断是否被删除。实现用线性基(或者枚举,反正c4cle 4)。

    现在就看怎么预处理树边的权值了。

    我们可以先把所有点ii的权值赋为跟ii相连的非树边的权值异或和。

    突然发现,只要把uu的子树的这个值全部异或起来,就是uu的父亲树边的权值。 因为一条非树边如果在子树内部会被异或两次,相当于去掉了。所以这道题就做完了。

    时间复杂度为O(n+kclogVal)O(n+kclog Val),此处ValValrandrand值域。

    顺便说一下,linux系统的RAND_MAX不是2^15-1

    CODE

    1000ms

    #include <bits/stdc++.h>
    using namespace std;
    char cb[1<<18],*cs,*ct;
    #define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
    inline void rd(int &x) {
    	x = 0; char ch; while(!isdigit(ch=getc()));
    	do x=x*10+ch-'0';while(isdigit(ch=getc()));
    }
    const int MAXN = 100005;
    const int MAXM = 200005;
    int n, m, fir[MAXN<<1], to[MAXM<<1], nxt[MAXM<<1], cnt = 1;
    inline void add(int u, int v) {
    	to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt;
    	to[++cnt] = u; nxt[cnt] = fir[v]; fir[v] = cnt;
    }
    int val[MAXN], wt[MAXM], dfn[MAXN], tmr;
    void dfs(int u, int ff) {
    	dfn[u] = ++tmr;
    	for(int i = fir[u], v; i; i = nxt[i])
    		if((v=to[i]) != ff) { //no same edge
    			if(!dfn[v]) {
    				dfs(v, u);
    				wt[i>>1] = val[v];
    				val[u] ^= val[v]; //xor sum
    			}
    			else if(dfn[u] > dfn[v]) { //to ancestor
    				wt[i>>1] = rand() + 1;
    				val[u] ^= wt[i>>1];
    				val[v] ^= wt[i>>1];
    			}
    		}
    }
    
    int b[31];
    inline void clr() { memset(b, 0, sizeof b); }
    inline bool ins(int x) {
    	for(int i = 30; i >= 0; --i)
    		if(x>>i&1) {
    			if(!b[i]) { b[i] = x; return 1; }
    			x ^= b[i];
    		}
    	return 0;
    }
    int main () {
    	srand(20030323);
    	rd(n), rd(m);
    	for(int i = 1, u, v; i <= m; ++i) rd(u), rd(v), add(u, v);
    	dfs(1, 0);
    	int c, x, k;
    	rd(k);
    	while(k--) {
    		rd(c); clr(); bool flg = 1;
    		while(c--) {
    			rd(x); 
    			if(flg && !ins(wt[x])) flg = 0;
    		}
    		puts(flg ? "Connected" : "Disconnected");
    	}
    }
    
  • 相关阅读:
    oppo手机永久打开USB调试模式
    一个简单的php分页类代码(转载)
    php分页类的二种调用方法(转载)
    直流电与交流电的几点区别
    变压器的分类_变压器的作用
    静态无功补偿与动态无功补偿的区别(转载)
    无功补偿装置三种投切方式(转载)
    西门子plc串口通讯方式
    电动葫芦起吊重物时摇晃怎么办?
    电灯节电小知识的方法大全(转载)
  • 原文地址:https://www.cnblogs.com/Orz-IE/p/12039214.html
Copyright © 2020-2023  润新知