随机话

随机话过题事非常 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(); 
}
View Code
  • 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");
}
View Code
  • 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(); }
View Code

 

posted @ 2022-10-23 00:03  铃兰星夜  阅读(202)  评论(1编辑  收藏  举报