UOJ 75 - 【UR #6】智商锁(矩阵树定理+随机+meet-in-the-middle)

题面传送门

首先看到生成树计数可以很显然地想到矩阵树定理,但是由于此题是根据生成树个数构造合法的解的方案,所以一看就没有什么正经的做法(有提答题内味儿了)。

一个很显然的性质是:对于一张图我们可以考虑将其进行边双连通分量缩点,那么最终的生成树个数显然等于所有边双连通分量中生成树的乘积。这就启发我们采用这样一个思想,将原来 \(100\) 个点的图拆成若干个部分,对每个部分进行随机化,然后再像串糖葫芦一样将这些部分串起来。

然后就是我所想不到的地方了,考虑随机生成 \(1000\) 张由 \(15\) 个顶点(原题解是 \(12\) 个点,不过我认为都差不多罢)组成的图,每条边都有 \(70\%\) 的概率出现,并用矩阵树定理计算其中生成树的数量,我们记 \(f_i\) 表示第 \(i\) 张图的生成树个数 \(\bmod 998244353\),那么我们考虑求出一个四元组 \((a,b,c,d)\) 满足 \(f_af_bf_cf_d\equiv k\pmod{998244353}\),那么我们把 \(a,b,c,d\) 四张图串成一条链,形成一个 \(60\) 个点的图即可。那么怎么找出符合要求的 \((a,b,c,d)\) 呢?考虑用 meet-in-the-middle 的思想,枚举二元组 \(a,b\) 并将 \(f_af_b\) 的值放入一个 hashtable,然后枚举 hashtable 中的元素 \(v\) 并查看 \(k·v^{-1}\) 是否在 hashtable 中,这样即可在 \(\mathcal O(10^6)\) 的时间内求出符合要求的 \((a,b,c,d)\)

最后我们要说明的问题是为什么这样做是正确的,或者说,为什么这个做法获得正确答案的概率很大。首先我们知道 \(15\) 个点的完全图中有 \(15^{13}\) 个生成树,该值是远远大于 \(998244353\) 的,也就是说这样的图的生成树个数在模上 \(998244353\) 中可以近似地看作均匀分布的,也就是说我们等价于随机生成了 \(1000\)\(<998244353\) 的数,而这 \(1000\) 个数两两配对共可以得到 \(\dbinom{1000}{4}\approx 10^{11}\) 个不同的数,也就是说本题能够获得正确答案的概率等价于 \(10^{11}\)\(<998244353\) 的数中包含全部 \(0\sim 998244352\) 的数的概率,简单想想就知道这个概率是很大的,如果真的要定量计算的话,那么每个数被覆盖的概率就是 \(1-(1-\dfrac{1}{998244353})^{10^{11}}\approx 1-e^{-100}\),该值大约是 \(1-10^{-44}\),这样一来所有数都被覆盖的概率大约就是 \((1-10^{-44})^{998244353}\),我用计算器摁了一下,\((1-10^{-28})^{10^9}\) 大约是 \(0.99997\),而前者显然比后者更大,因此正确性是可以保证的。

最后注意特判 \(k=0\),因为生成树个数为 \(998244353\) 的倍数的概率显然远远小于不是 \(998244353\) 的倍数。

const int MOD=998244353;
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
struct graph{
	bool has[17][17];
	int a[17][17],cnt;
	void add(int x,int y){
		x--;y--;
		a[x][y]=(a[x][y]-1+MOD)%MOD;
		a[y][x]=(a[y][x]-1+MOD)%MOD;
		a[x][x]=(a[x][x]+1)%MOD;
		a[y][y]=(a[y][y]+1)%MOD;
	}
	int getdet(){
		int sgn=1;
		for(int i=1;i<15;i++){
			int t=i;
			for(int j=i+1;j<15;j++) if(a[j][i]) t=j;
			if(t!=i) sgn=-sgn;
			for(int j=i;j<15;j++) swap(a[i][j],a[t][j]);
			int iv=qpow(a[i][i],MOD-2);
			for(int j=i+1;j<15;j++){
				int mul=1ll*(MOD-a[j][i])*iv%MOD;
				for(int k=i;k<15;k++) a[j][k]=(a[j][k]+1ll*mul*a[i][k])%MOD;
			}
		} int res=(sgn+MOD)%MOD;
		for(int i=1;i<15;i++) res=1ll*res*a[i][i]%MOD;
		return res;
	}
	void gen(){
		for(int i=1;i<=15;i++) for(int j=1;j<i;j++){
			int pro=rand()%10;
			if(pro>2) has[i][j]=has[j][i]=1,add(i,j);
		} cnt=getdet();
	}
} g[1005];
map<int,pii> mul;
int main(){
	srand(20060729);
	for(int i=1;i<=1000;i++) g[i].gen();
	for(int i=1;i<=1000;i++) for(int j=1;j<=i;j++)
		mul[1ll*g[i].cnt*g[j].cnt%MOD]=mp(i,j);
	int qu;scanf("%d",&qu);
	while(qu--){
		int t;scanf("%d",&t);
		if(!t){printf("%d %d\n%d %d\n",3,1,1,2);continue;}
		for(map<int,pii>::iterator it=mul.begin();it!=mul.end();it++){
			int val=it->fi,rst=1ll*t*qpow(val,MOD-2)%MOD;
			if(mul.count(rst)){
				vector<pii> ans;
				int a=it->se.fi,b=it->se.se,c=mul[rst].fi,d=mul[rst].se;
//				printf("%d %d %d %d\n",a,b,c,d);
//				printf("%d %d %d %d\n",g[a].cnt,g[b].cnt,g[c].cnt,g[d].cnt);
				for(int i=1;i<=15;i++) for(int j=1;j<i;j++) if(g[a].has[i][j]) ans.pb(mp(i,j));
				for(int i=1;i<=15;i++) for(int j=1;j<i;j++) if(g[b].has[i][j]) ans.pb(mp(15+i,15+j));
				for(int i=1;i<=15;i++) for(int j=1;j<i;j++) if(g[c].has[i][j]) ans.pb(mp(30+i,30+j));
				for(int i=1;i<=15;i++) for(int j=1;j<i;j++) if(g[d].has[i][j]) ans.pb(mp(45+i,45+j));
				ans.pb(mp(1,16));ans.pb(mp(16,31));ans.pb(mp(31,46));
				printf("%d %d\n",60,ans.size());
				for(pii e:ans) printf("%d %d\n",e.fi,e.se);
				break;
			}
		}
	}
	return 0;
}
posted @ 2021-05-02 22:09  tzc_wk  阅读(162)  评论(0编辑  收藏  举报