随机话
随机话过题事非常 exciting 的事!
在这里记录我写过的那些有(qi)趣(qi)好(guai)玩(guai)的随机化
- CF1726D Edge Split
题目大意:对一个 $n$ 个点 $m$ 条边的无向简单图,要对它进行红蓝染色,使得 仅由红边 组成的无向图连通块数 + 仅由蓝边 组成的无向图连通块数 最小。
$n\le 10^6,m \le n+2$
根据奇怪的数据范围,以及我们要尽量减少环的目标,容易想到的是染一棵生成树,再考虑剩下的 $3$ 条边。
我们发现只要这 $3$ 条边没有成环,我们得到的答案就是正确的。
因此问题就是:怎样让它染一颗生成树后,剩下三个点不是一个环。
普遍的解法是随便染出一棵生成树,再把环上的某条边与生成树里的边进行调整断环。
但是随机化是万能的!我们发现如果我们以不同的顺序构建生成树,得到的剩下三个点也很可能不同,那么我们对边随机排序几次再染生成树,就能过掉这题了!(
简单证明一下复杂度:
我们发现如果形成了三元环 $(i,j,k)$,那对于任意一个点(我们就以 $i$ 来说),都在生成树上有一条另外的在生成树上的连边。
于是我们对于每一个点,打乱一次后这个环被拆散的概率不会低于 $\frac{2}{3}$
容易证明的是在 $m=n+2$ 情况下最多只有两个不同的三元环
那我们期望在几次内显然可以得到答案,时间复杂度即可看作 $O(m \log m)$
(上面这一段可能比较胡扯,如果有问题欢迎指出)
#include<bits/stdc++.h> using namespace std; const int N=1000005; inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x*f; } int n,m; mt19937 rnd(10086001); uniform_int_distribution<int>lim(1,100000000); struct E{ int u,v,w,id; }a[N]; int col[N]; bool cmp(E a,E b){ return a.w>b.w; } int fa[N],xx[N],cnt; int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]);} void calc(){ for(int i=1;i<=m;i++) a[i].w=lim(rnd); sort(a+1,a+m+1,cmp); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) if(find(a[i].u)^find(a[i].v)) fa[find(a[i].u)]=find(a[i].v),col[a[i].id]=0; else col[a[i].id]=1; for(int i=1;i<=n;i++) xx[i]=0; for(int i=1;i<=m;i++) if(col[a[i].id]){ xx[a[i].u]++; xx[a[i].v]++; } cnt=0; for(int i=1;i<=n;i++) if(xx[i]>0) cnt++; } void solve(){ n=read(),m=read(); for(int i=1;i<=m;i++) a[i].u=read(),a[i].v=read(),a[i].id=i; calc(); while(cnt==3) calc(); for(int i=1;i<=m;i++) printf("%d",col[i]); puts(""); } main(){ int tc=read(); while(tc--) solve(); }
- CF1523D Love-Hate
- CF1305F Kuroni and the Punishment
- ABC272G Yet Another mod M
题目大意:给你一个长度为 $n$ 的序列 $a$,找到一个模数 $m$ 使得所有 $a_i$ 对 $m$ 取模后序列内会产生绝对众数。 $ n \le 5000 , a_i \le 10^9$。
考虑如果我一个数 $a_i$ 在答案集合里,且模数为 $m$,那么对于集合里的所有数 $a_j$,均有 $m \mid a_i-a_j$。
由于答案集合的大小超过 $\frac{n}{2}$,因此我们随机 $30$ 次 $a_i$ ,错误的概率比你现在一秒后当场暴毙还低。
但是现在我们知道了 $a_i$,怎么找到这个 $m$ 呢?很简单,由于答案集合的大小超过 $\frac{n}{2}$,因此我们再随机 $30$ 次 $a_j$,再对 $|a_i-a_j|$ 分解质因数,判断每个质因数能否成为 $m$ 即可。
总结:绝对众数 = 随机。
总时间复杂度 $O(T^2 n \log V)$,$T$ 为随机次数,我写的取 $T=30$。
#include<bits/stdc++.h> using namespace std; const int N=5005; inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x*f; } int n,a[N],b[N]; mt19937 rnd(10086001); void solve(int base,int x){ for(int i=1;i<=n;i++) b[i]=abs(a[i]-base); x=b[x]; for(int i=3;i*i<=x;i++){ if(x%i==0){ while(x%i==0) x/=i; int ok=0; for(int j=1;j<=n;j++) ok += (b[j]%i==0); if(ok*2>n){ printf("%d\n",i); exit(0); } } } if(x>2){ int ok=0; for(int j=1;j<=n;j++) ok += (b[j]%x==0); if(ok*2>n){ printf("%d\n",x); exit(0); } } } main(){ n=read(); for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1); uniform_int_distribution<int>lim(1,n); for(int t1=1;t1<=30;t1++){ int p = lim(rnd); for(int t2=1;t2<=30;t2++) solve(a[p],lim(rnd)); } puts("-1"); }
- BZOJ 2396 神奇的矩阵
题目大意:$T$ 组数据,每次给你三个 $n \times n$ 的矩阵 $A,B,C$,判断 $ A \times B = C$ 是否成立。 $n\le 1000 , T \le 5$。
多组测试,$T\le 5, n\le 1000,num \le 1000$
直接暴力矩阵乘法是 $O(T \times n^3)$,肯定会寄。
考虑随机化一个向量($1 \times n$ 的矩阵)$D$,如果 $A \times B = C$,那么必然有 $A \times D \times B = C \times D$
由于 $D$ 是 $1 \times n$ 的,矩乘的复杂度直接降到了 $O(n^2)$
但注意这是必要不充分条件,所以我们多跑几轮即可,时间复杂度 $O(TKn^2)$,$K$ 为随机次数,我写的取 $K=3$。
#include<bits/stdc++.h> using namespace std; const int N=1005; inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x*f; } int n,a[N][N],b[N][N],c[N][N]; long long d[N],tmp[N],AB[N],C[N]; mt19937 rnd(10086001); uniform_int_distribution<int>lim(1,1000); bool check(){ for(int i=1;i<=n;i++) d[i] = lim(rnd) , tmp[i] = AB[i] = C[i] = 0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) tmp[i] += b[i][j] * d[j]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) AB[i] += a[i][j] * tmp[j]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) C[i] += c[i][j] * d[j]; for(int i=1;i<=n;i++) if(AB[i] ^ C[i]) return 0; return 1; } void solve(){ for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) b[i][j]=read(); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) c[i][j]=read(); if(check() && check() && check()) puts("Yes"); else puts("No"); } main(){ while(cin>>n) solve(); }