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

2 篇文章 0 订阅
1 篇文章 0 订阅

题目

题目链接

记得数据范围在HINT处

题解1

直接离线分治

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

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

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

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

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

O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
这能叫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

分治的做法但是很慢

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

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

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

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

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

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

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

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

时间复杂度为 O ( n + k c log ⁡ V a l ) O(n+kc\log Val) O(n+kclogVal),此处 V a l Val Val r a n d rand rand值域。

顺便说一下,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");
	}
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值