用随机化算法通过网络流

· · 算法·理论

头一天在日本作家结城浩所著的《数学女孩4,随机算法》看到了 3-SAT 的 O(1.334^n) 做法,第二天恰好做到了这题,想到了一个随机化算法。

观察问题

题目传送门

简析题意:要想使第 x 个球队获得冠军,须使第 x 个球队赢下其他所有球队,并且能够构造一种方案使其他队伍的获胜场次小于等于第 x 个球队的获胜场次。

s_i 为第 i 个球队剩余的总场次,则有一个很浅显的结论:若 a_x+s_x<a_i,那么第 x 个球队一定不可能成为冠军。

我们发现,对于每个 x,如果暴力枚举除 x 以外的每场比赛的结果,复杂度实际上并不高,那么就可以尝试使用随机化算法解决。

如果对于任意的 i\ne x,都有 a_x+s_x \ge a_i,那么就试图构造一种方案使第 x 个球队成为冠军。这里我们采用单向正确的随机化算法,若程序返回“找到方案可以成为冠军”,那么输出 x;否则程序返回“未找到方案大概不可以成为冠军”,那么我们认为第 x 个球队不可能成为冠军。

考虑构造方案

引入一个 汉明距离 的概念:汉明距离(Hamming Distance)是用于衡量两个等长字符串(在数据通信中通常是二进制串)之间差异程度的一个度量。它计算的是两个字符串在相同位置上不同字符(或比特)的数量。

A 为第 x 个球队能够成为冠军的一个方案,且有关 x 的比赛 x 全赢。

B 是我们随机构造的一个方案,满足有关 x 的比赛 x 全赢,其他关于 ij 比赛随机决定 i , j 各赢几场。

先来探讨一个问题,记 disAB 的距离,其计算方式如下:

$$dis=\sum_{1\le i < j \le n , i,j \ne x} \lvert f_{i,j} - g_{i,j} \rvert $$ 问 $dis$ 最坏情况下的期望值是多少? 期望可以看作平均,最坏情况是 $a_{i,j}=10$,$f_{i,j}=0$,此时 $g_{i,j}$ 的期望值为 $5$,$dis=24 \times 23 \times 5=2760$。 到这里,就会发现可以写出一种与 3-SAT 的 $O(1.334^n)$ 做法类似的实现方案,写一个 $Check$ 函数,见下: 1. 从 $1$ 循环到 $n$ ,设 $op=a_x+s_x$,如果发现 $op<a_i$,返回 $0$。 2. 随机构造一种方案 $B$,并求出 $g$ 数组。 3. 检查此时是否有队伍获胜次数超过 $x$ 的获胜次数,如果没有,返回 $1$。 4. 在获胜次数超过 $x$ 的队伍中随机寻找一个队伍 $i$。 5. 在满足 $g_{i,j} \ne 0$ 且 $j \ne x$ 的 $j$ 中随机取一个 $j$,修改 $i$ 和 $j$ 的比赛情况,具体地,让 $j$ 多赢 $i$ 一场,即 $g_{i,j}$ 减掉 $1$,$g_{j,i}$ 加上 $1$。 6. 执行 3~5 步 $5000$ 遍。 7. 回到第二步,第二步总共执行 $100$ 次。 8. 返回 $0$,表示第 $x$ 个球队不可能成为冠军。 ### 对下界的评估 随机化算法的时间计算关键在于,在有解的情况下单次循环能够找到答案的概率。在本算法中,就是每一次从第二步开始能够返回 $1$ 的概率。 这里给出两种评估方案: - $dis$ 最坏情况下期望为 $2760$,每一次调整会使 $dis$ 增加或减少 $1$。实际上 $A$ 很可能有多个,因此实际的效率会比这个小很多。 - 记 $s_i$ 为第 $i$ 个球队当前状态下赢的总场数,$dis'$ 为其他 $s_i$ 超过 $op$ 的总和,即有 $$ dis'=\sum_{1\le i \le n,s_i>op,i\ne x} (s_i-op) $$ 每次操作只有可能使 $dis'$ 减少 $1$ 或不变,我能想到的比较极端的情况是 $dis'=210$(期望值是 $105$),每次有 $\frac{1}{23}$ 的概率使 $dis'$ 减小。 很抱歉的是我并不能更加准确地评估出它的下界,只能这样了。 ### code (码风非常不好,又臭又长) ```cpp #include<cstdio> #include<algorithm> #include<time.h> using namespace std; const int nn=30; int n; int a[nn],b[nn]; int c[nn][nn]; int sumc[nn]; bool Check(int); int s2[nn]; bool cmpd(int,int); int main() { unsigned int seed=(unsigned)time(NULL); srand(seed); // printf("seed=%u\n",seed);// scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { scanf("%d",&c[i][j]); sumc[i]+=c[i][j]; } bool ans[nn]; for(int i=1;i<=n;i++) ans[i]=Check(i); for(int i=1;i<=n;i++) if(ans[i]) printf("%d ",i); return 0; } bool Check(int x) { int c2[nn][nn]; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) c2[i][j]=c[i][j]; s2[i]=sumc[i]; } int op=a[x]+s2[x]; for(int i=1;i<=n;i++) { s2[i]-=c2[i][x]; c2[i][x]=c2[x][i]=0; } for(int i=1;i<=n;i++) if(a[i]>op&&i!=op) return 0; for(int qw1=0;qw1<=100;qw1++) { int s3[nn],c3[nn][nn]; for(int i=0;i<nn;i++) s3[i]=a[i]; for(int i=1;i<=n;i++) { if(i==x) continue; for(int j=i+1;j<=n;j++) { if(j==x) continue; c3[i][j]=rand()%(c2[i][j]+1); c3[j][i]=c2[i][j]-c3[i][j]; s3[i]+=c3[i][j]; s3[j]+=c3[j][i]; } } for(int qw2=0;qw2<=5000;qw2++) { int team1[nn],tail1=0; for(int i=1;i<=n;i++) if(s3[i]>op&&i!=x) team1[tail1++]=i; if(!tail1) return 1; int i=team1[rand()%tail1]; int team2[nn],tail2=0; for(int j=1;j<=n;j++) if(c3[i][j]&&j!=x) team2[tail2++]=j; int j=team2[rand()%tail2]; s3[i]--,s3[j]++; c3[i][j]--,c3[j][i]++; } } return 0; } bool cmpd(int x,int y) { return s2[x]>s2[y]; } ```