在讲随机算法之前,首先要介绍“random walks on a line”,假设我们在数轴的点0的位置,在点0的话,下一个时刻我们会移动到点1上,如果在其他的点x,我们有50%的概率下一个时刻移动到点x-1上,有50%的概率移动到x+1上。现在让我们来算一下,从点0开始移动到点n,期望需要移动多少步。这个期望的算法和之前写的这篇的方法差不多。设E(i)是从点i(i<=n)移动到点n所需要的期望的步数。于是我们有
E(n)=0
E(0)=E(1)+1
E(i)=0.5∗(1+E(i+1))+0.5∗(1+E(i−1))
有上面的式子我们可以得到最后的结果,推导比较简单,就不赘述了
E(0)=n2
设Tn表示从0走到n的步数,于是我们有
n2=E(Tn)=∑2n2k=0(k∗P(Tn=k))+∑∞k=2n2+1(k∗P(Tn=k))
因为 ∑2n2k=0(k∗P(Tn=k))≥0
所以 n2≥∑∞k=2n2+1(k∗P(Tn=k))
≥∑∞k=2n2+1((2n2)∗P(Tn=k))
=2n2∗P(Tn>2n2)
于是我们得到
P(Tn>2n2)≤12
也就是说我们从0开始走2*n*n步,我们有大于1/2的概率已经经过n这个点了。
ok回到我们的2-sat问题。
我们的算法是这样的,我们开始时随机给变量赋值0或者1,然后迭代足够多的次数(大于2*n*n的次数),如果某次迭代,我们找到了答案就可以返回结果了,如果我们迭代足够多的次数都没找到答案就返回无解。每次迭代,我们找一个不满足要求的子式,然后两个变量随机挑一个改变他的值。
假设我们现在的解是a,满足要求的解是a*,那么我们的解和满足要求的解,值不同的变量的个数是x,某次迭代,我们找到一个子式(xi or xj)不满足条件,因为子式不满足条件,所以xi和xj不可能同时和a*里值是相同的,如果两个值都不同,那么我们这次改动使得x=x-1,如果有一个值不同,那么我们有50%的概率使得x=x-1有50%的概率使得x=x+1,所以,答案变好的概率要大于50%,所以迭代2*n*n次能得到解的概率就会小于1/2。
很有趣的算法,并且我们用这个算法,可以AC掉poj 3648这道题!看代码
-
#include <cstdio>
-
#include <cstdlib>
-
#include <cstring>
-
#include <ctime>
-
-
const int maxn = 10010;
-
int n, m, a, b;
-
char c1, c2;
-
int ans [maxn ], record [maxn ] [ 2 ];
-
-
int get ( int x ) {
-
if (x<n )
-
return ans [x ];
-
return !ans [x -n ];
-
}
-
int main ( ) {
-
//srand(time(NULL));这里如果调用time,poj会报 runtime error的错误
-
srand ( 7 );
-
while ( 1 ) {
-
scanf ( "%d%d", &n, &m );
-
memset (ans, 0, sizeof ans );
-
if ( !n &&!m )
-
break;
-
for ( int i = 0; i<m; ++i ) {
-
scanf ( "%d%c%d%c", &a, &c1, &b, &c2 );
-
if (c1 == 'w' )
-
a += n;
-
if (c2 == 'w' )
-
b += n;
-
record [i ] [ 0 ] = a;
-
record [i ] [ 1 ] = b;
-
}
-
int mark = 0;
-
for ( int i = 0; i< 5 *n *n; ++i ) {
-
mark = 1;
-
for ( int j = 0; j<m; ++j )
-
if ( (record [j ] [ 0 ] %n !=record [j ] [ 1 ] %n ) &&! (get (record [j ] [ 0 ] )||get (record [j ] [ 1 ] ) ) ) {
-
int tmp = rand ( ) % 2;
-
if (record [j ] [ 0 ] %n == 0 )
-
tmp = 1;
-
if (record [j ] [ 1 ] %n == 0 )
-
tmp = 0;
-
ans [record [j ] [tmp ] %n ] = 1 -ans [record [j ] [tmp ] %n ];
-
mark = 0;
-
break;
-
}
-
if (mark )
-
break;
-
}
-
if ( !mark ) {
-
printf ( "bad luck\n" );
-
continue;
-
}
-
for ( int i = 1; i<n; ++i ) {
-
if (ans [i ] )
-
printf ( "%dh", i );
-
else
-
printf ( "%dw", i );
-
if (i<n -1 )
-
printf ( " " );
-
else
-
printf ( "\n" );
-
}
-
}
-
return 0;
-
}
记得之前gcj曾经有道题目需要用到生日悖论来解决,感觉和这个随机算法都是很有趣的算法阿。