题解:P7105 「C.E.L.U-01」门禁

P7105 「C.E.L.U-01」门禁

meet in the middle 折半搜索练习。参考题解

\(O(3^n)\) 做法。

\(f_S\)\(S\) 中点集构成一个连通块的概率。

则有套路容斥:考虑这个点集不是连通块时,枚举点集中编号最小的那个点所在的连通块,要求这个连通块自己连通,并且和剩余的点之间没有边。

可以得到转移式:

\[f_S=1-\sum_{T\subset S}f_TF(S\backslash T,T) \]

其中 \(F(S,T)\)\(S\)\(T\) 之间没有边的概率。\(T\) 包含编号最小的那个点。

暴力计算将得到一个 \(O(3^nn^2)\) 的做法。

为了优化掉后面那坨东西,考虑能不能预处理掉。考虑折半:预处理出 4 个数组 \(s_{0/1,0/1,x,y}\)\(x\)\(y\) 分别对应的点集区间是:

  • \([0,n/2)\)\([0,n/2)\)
  • \([0,n/2)\)\([n/2,n)\)
  • \([n/2,n)\)\([0,n/2)\)
  • \([n/2,n)\)\([n/2,n)\)

数组记录点集 \(x\) 和点集 \(y\) 之间不连通的概率。

这样暴力预处理这 4 个数组的时间是 \(O(2^nn^2)\),同时可以通过拆分集合,把对应的 \(s\) 相乘来 \(O(1)\) 查询 \(F(S,T)\)

如此继续暴力执行 DP 即可做到 \(O(3^n)\),而且常数很小。这一做法可以推广到类似的状压连通块的题目,且均可以做到 \(O(2^nn^2+3^n)\)

实现有一些细节:处理 s 的时候如果是减去一个下标状压的,注意 \(s\) 数组的 \(x & y\) 不一定为 \(0\),因为 \(x\)\(y\) 即使一样也不一定对应的是同一个点集,原点集需要给所有点减去一个下标,而减去的下标不一定相同。

点击查看代码
#include <bits/stdc++.h>
typedef long double lb;
using namespace std;
const int N2 = 1000010,N = 20;
int n,m;
lb p[N][N],g[2][2][1010][1010],f[N2];
void init(int a,int b){
	int x = 0,y = 0,ux = m - 1,uy = m - 1;
	if(a == 1)x = m,ux = n - 1;
	if(b == 1)y = m,uy = n - 1;
	for(int i = 0; i < (1 << (ux - x + 1)); i++){
		for(int j = 0; j < (1 << (uy - y + 1)); j++){
			g[a][b][i][j] = 1;
			for(int ia = x; ia <= ux; ia++){
				if(!(1 & (i >> (ia - x))))continue;
				for(int ib = y; ib <= uy; ib++){
					if(!(1 & (j >> (ib - y))))continue;
					g[a][b][i][j] *= (1.0 - p[ia + 1][ib + 1]);
				}
			}
		}
	}
}
lb calc(int x,int y){
	int ax = x & ((1 << m) - 1),bx = (x >> m);
	int ay = y & ((1 << m) - 1),by = (y >> m);
	return g[0][0][ax][ay] * g[0][1][ax][by] * g[1][0][bx][ay] * g[1][1][bx][by];
}
int main(){
	scanf("%d",&n);
	m = n / 2;
	for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)scanf("%Lf",&p[i][j]);
	init(0,0),init(0,1),init(1,0),init(1,1);
	f[0] = 1;
	for(int i = 0; i < (1 << n); i++){
		f[i] = 1.0;
		int k = i ^ (i & (-i));
		for(int j = k; j; j = (j - 1) & k){
			f[i] -= f[i ^ j] * calc(i ^ j,j);
		}
	}
	lb ans = 0;
	for(int i = 1; i < (1 << n); i++)ans += f[i] * calc(i,(1 << n) - 1 - i);
	printf("%.6Lf",ans);
	return 0;
}
posted @ 2026-06-30 17:59  Jenny_yu  阅读(15)  评论(0)    收藏  举报