• uoj75


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

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

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

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

    随机生成图然后矩阵树的话,跑一次复杂度是 (mathrm O!left(n^3 ight))​,很容易设计出方案:可以设计成跑 (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<<"
    ";
    			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<<"
    ";
    			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<<"
    ";
    			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<<"
    ";
    			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<<"
    ";
    			puts("1 21");
    			puts("21 41");
    			puts("41 61");
    			return;
    		}
    	}
    }
    int main(){
    	int testnum;
    	cin>>testnum;
    	while(testnum--)mian();
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    操作元素
    CSS3 制作网格动画效果
    网页边栏过渡动画
    超炫的 CSS3 页面切换动画效果
    多种鼠标悬停效果
    各种 SVG 制作单选和多选框动画
    全屏遮罩层效果(10种)
    FancyBox – 经典 Lightbox 效果插件
    CSS3 & SVG 制作钟表
    20种新颖的按钮风格和效果【附源码】
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-uoj75.html
Copyright © 2020-2023  润新知