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;
}