[ABC-ZONe-F]Encounter and Farewell

壹、题目描述 ¶

传送门 to Atcoder

贰、题解 ¶

直接宅 \(\sf OneInDark\) 的了,他的在这里

考虑 \(S\) 的补集 \(\overline S\). 这玩意儿相当于,你每次移动可以使得编号异或一个 \(\overline S\) 内的数。

树是什么呢?无环且连通即可。那么 \(a,b\) 之间是否连通呢?相当于 \(a\) 能不能经过若干次异或变成 \(b\) 呢?相当于 \(a\oplus b\) 能不能被 \(\overline S\) 中的元素凑出呢?

这就是线性基裸题。有解的充要条件是 \(1\)\(2^n-1\) 每个数都可以被凑出,即线性基是满秩的(全部填满了)。

那么怎么构造方案呢?其实很简单。我们知道线性基里的数字可以凑出 到 的所有数字。那么它们对应的原数是不是也行呢?

显而易见,一目了然。考虑归纳。线性基中新加入的元素 \(=\) 某个原数 \(\oplus\) 线性基中若干数字 \(=\) 某个原数 \(\oplus\) 线性基中若干数字对应的原数 \(=\) 若干原数的异或和。

所以,我们只保留边权为 “原数” 的边,剩下的图仍然连通。连通图怎么求生成树?搞个并查集,直接枚举每一条边就行了……

时间复杂度 \(\mathcal O(n2^n)\),好写的很。

官方题解

有一个神奇的改进方法:利用 格雷码。格雷码的 \(1\) 相当于 “使用线性基中的这个元素” 。显然格雷码不同,得到的结果不同。

而格雷码是一条链,恰好包含了 \(2^n\) 个数,直接连接成一条链即可。相邻数字只差一个 \(\rm bit\) ,保证了边权是线性基里的数(对应的原数)。

格雷码如何生成呢?其实格雷码的第 \(i-1\) 个和第 \(i\) 个的异或和就是 \(\text{lowbit}(i)\) 啊。

叁、参考代码 ¶

#define Endl putchar('\n')
#define mp(a, b) make_pair(a, b)
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define fep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
typedef long long ll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int maxn=18;

int n, m, N;

int forbid[1<<maxn];

inline void input(){
	N=readin(1), m=readin(1);
	for(n=0; (2<<n)<N; ++n);
	rep(i, 1, m) forbid[readin(1)]=1;
}

// linear algebra
int c[maxn+5], refl[maxn+5];

namespace ufs{
	int fa[1<<maxn];
	inline void init(){
		rep(i, 0, N-1) fa[i]=i;
	}
	int find(int u){
		return fa[u]==u? u: fa[u]=find(fa[u]);
	}
	inline int merge(int u, int v){
		if((u=find(u))==(v=find(v))) return 0;
		return fa[u]=v, 1;
	}
}

signed main(){
	input();
	rep(i, 1, N-1) if(!forbid[i]){
		int x=i;
		for(int j=n; ~j && x; --j) if((x>>j)&1){
			if(!c[j]) c[j]=x, refl[j]=i;
			x^=c[j];
		}
	}
	rep(i, 0, n) if(!c[i])
		return printf("-1\n"), 0;
	ufs::init();
	rep(i, 0, N-1) rep(j, 0, n)
		if(ufs::merge(i, i^refl[j])){
			printf("%d %d\n", i, i^refl[j]);
		}
	return 0;
}
posted @ 2021-05-03 08:47  Arextre  阅读(61)  评论(0编辑  收藏  举报