uoj75 - 智商锁

神仙题 %%%%%%%%%%%%%%%%%%%%%%%%%,我智商确实被锁了

首先没有什么正经做法是显然的吧(前提是随机化不算正经)

考虑随机化。每次随机搞一个图,计算生成树个数,希望 \(\bmod 998244353\) 是均匀随机的。这样正确率是 \(1-\left(\dfrac{p-1}p\right)^x\),当 \(x\) 大概是 1e10 的时候正确率 \(>0.9999\)

但是咱也没法跑 1e10 遍啊。注意到一个图的生成树个数是可以分解的:把割边拆开,原图生成树个数显然等于两个连通分量的生成树个数乘积。那么我们用这个思路:跑 2e5 遍,前 1e5 和后 1e5 两两组合,就得到了 1e10,而且依然是均匀随机的。

随机生成图然后矩阵树的话,跑一次复杂度是 \(\mathrm O\!\left(n^3\right)\)​,很容易设计出方案:可以设计成跑 \(4\)​ 组 \(317\)​,每次 \(n=20\)​ 个点,完全图上每条边出现概率为 \(0.5\)​​(经证实这样的程度已经达到均匀随机的要求)。

那怎么找到答案乘积等于给定值的四元组呢?直接枚举四元组肯定不行,这样复杂度依然是 1e10。事实上这类问题有个类似于 BSGS、折半搜的 trick(比较简单就是了),学名好像叫 meet in the middle,一般能把复杂度开方(或者分解成其它的乘数对相加)。这题的话,就枚举两半的二元组,各得到 1e5 的数组,然后对一个进行扫描,在另一个里面找 \(mx^{-1}\)​,map 即可实现。

注意要特判 \(m=0\),因为在 \(0\) 上的概率分布是特殊的,很低很低,直接输出 2 0 即可(一开始还输成了 1 0,殊不知那样有 \(1\) 棵生成树)。

code(为了庆祝今天的圣诞节,随机种子使用了第零次圣诞节的日期)
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
const int mod=998244353;
mt19937 rng(19260817);
int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return res;
}
int inv(int x){return qpow(x,mod-2);}
const int N=30;
int n=20,m;
int eg[1500][N][N];
int a[N][N],det[1500];
void swp(int x,int y){
	for(int i=1;i<=n;i++)swap(a[x][i],a[y][i]);
}
void tadd(int x,int v,int y){
	for(int i=1;i<=n;i++)(a[y][i]+=1ll*v*a[x][i]%mod)%=mod;
}
int gauss(){
	int ans=1;
	for(int i=1;;i++){
		int row=0,col=0;
		for(int j=1;j<=n;j++){
			for(int k=i;k<=n;k++)if(a[k][j]){row=k,col=j;break;}
			if(row)break;
		}
		if(!row)break;
		swp(i,row);if(i!=row)ans*=-1;
		int iv=inv(a[i][col]);
		for(int j=i+1;j<=n;j++)if(a[j][col])tadd(i,-1ll*a[j][col]*iv%mod,j);
	}
	for(int i=1;i<=n;i++)ans=1ll*ans*a[i][i]%mod;
	return ans;
}
void mian(){
	cin>>m;
	if(m==0)return puts("2 0"),void();
	for(int i=1;i<=1268;i++){
		memset(a,0,sizeof(a));
		for(int j=1;j<=n;j++)for(int k=j+1;k<=n;k++){
			a[j][k]=a[k][j]=eg[i][j][k]=eg[i][k][j]=uniform_int_distribution<>(-1,0)(rng);
			a[j][j]-=eg[i][j][k],a[k][k]-=eg[i][j][k];
		}
		n--;
		det[i]=gauss();
		n++;
	}
	map<int,pair<int,int> > p;
	for(int i=635;i<=951;i++)for(int j=952;j<=1268;j++){
		int x=1ll*det[i]*det[j]%mod;
		p[x]=mp(i,j);
	}
	for(int i=1;i<=317;i++)for(int j=318;j<=634;j++){
		int x=1ll*m*inv(1ll*det[i]*det[j]%mod)%mod;
		if(p.find(x)!=p.end()){
			pair<int,int> pp=p[x];
			int k=pp.X,o=pp.Y;
			int cnt=0;
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)cnt-=eg[i][y][z];
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)cnt-=eg[j][y][z];
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)cnt-=eg[k][y][z];
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)cnt-=eg[o][y][z];
			cout<<"80 "<<cnt+3<<"\n";
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)if(eg[i][y][z])cout<<y+0<<" "<<z+0<<"\n";
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)if(eg[j][y][z])cout<<y+20<<" "<<z+20<<"\n";
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)if(eg[k][y][z])cout<<y+40<<" "<<z+40<<"\n";
			for(int y=1;y<=n;y++)for(int z=y+1;z<=n;z++)if(eg[o][y][z])cout<<y+60<<" "<<z+60<<"\n";
			puts("1 21");
			puts("21 41");
			puts("41 61");
			return;
		}
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
posted @ 2021-08-17 21:45  ycx060617  阅读(69)  评论(2编辑  收藏  举报