ACM算法系列——google code jam 2011 Problem D GoroSort

     题目:http://code.google.com/codejam/contest/dashboard?c=975485#s=p3&a=3

     看了最终的Analysis以后,发现自己当时的思维陷入了一个死胡同。

     错误的想法:

     当时我是这么想的,假设只有两个数字的时候,即 2, 1的情况下,那么拍一次出现的情况为2,1 或者1,2. 则正确排序的情况为1/2。

     假设只有三个全部乱序的情况下(三个数字中,如果一个是正确,两个乱序的情况,按照上面一种情况考虑,如1,3,2,则按住1,然后3,2产生的情况和上面一样。),拍出来的情况有1,2,3.     1,3,2.     2,1,3.      2,3,1.     3,1,2.     3,2,1. 他们的概率都是1/6。 然后其中1,3,2.      2,1,3。       3,2,1. 都已经有一个排好序了,那么只要按住排好序的那个,接着按照两个乱序的情况进行排序,就可以了,因此概率为(1/6+1/6+1/6) * 1/2 = 3/ 12, 两种情况相加,能够排好序的概率为5/12。因此期望值为 2.4次。

     因此可以得出递推公式:

     g(n) =  P(n,n) + p(n, n-2) * g(2) + p(n, n-3) * g(3) + …… + p(n, 1) * g(n-1)。 p表示n个数组中,包含i个完全排序的情况。g(n)表示概率。

     因此这道题目的思路为,先求出不在同一个位置的连通圈,然后每个连通圈独自进行交换。如2 1 4 3. 连通圈为(2,1)(4,3)。先按住其中一组,拍两次,得到1,2 (4,3),再拍两次得到1,2,3,4. 因为平均四次就有答案了。

      代码如下:

#include<iostream> #include <fstream> #include <iomanip> using namespace std; const int N = 1001; ifstream fin("gor_small.in"); ofstream fout("gor_small.out"); int cas, index = 1; int fac[N]={0}; int wfac[N][N] = {0}; double g[N]; double result=0.0; int arr[N]; int n; int used[N]; void init() { for (int i=0; i< N; i ++) { g[i] = -1.000000; } g[1] = 0.0000000; for (int i=1;i < N; i ++) { wfac[i][i-1] = 1; } fac[0] = 1; for (int i=1; i < N; i++) { fac[i] = i * fac[i-1]; } } void read() { fin >> n; memset(used, 0, sizeof(int) * N); for (int i=1;i <= n; i ++) { fin >> arr[i]; } result =0.0000000; } int loadpath(int p) { if (used[p] == 1) { return 0; } used[p] = 1; int len = loadpath(arr[p]) +1; return len; } int calwfac(int l, int k) { int number = fac[l-k]; for (int i=l-k-1; i >= 1; i --) { if (wfac[l-k][i] ==0) { wfac[l-k][i] = calwfac(l-k, i); } number -= wfac[l-k][i]; } int temp = fac[l] / fac[k] / fac[l-k]; number = number * temp; return number; } double calg(int l) { double cg = 1.000000; for (int i=1; i < l-1; i ++) { if (wfac[l][i] == 0) { wfac[l][i] = calwfac(l, i); } if (g[l-i] < 0) { g[l-i] = calg(l-i); } cg += wfac[l][i] * g[l-i]; } return cg / fac[l]; } void gorosort() { for (int i=1; i <= n; i ++) { if (used[i] == 0) { int l = loadpath(i); if (l >= 2) { if (g[l] < 0) { g[l] = calg(l); } result += 1/g[l]; } }//end if used[i] } } int main() { init(); fin >> cas; while (index <= cas) { read(); gorosort(); fout<< "Case #"<<index<<": "<< setiosflags(ios::fixed) << setprecision(6) << result << endl; index ++; } return 0; } 

    当时,觉得自己的想法肯定没有错。结果就是没有办法AC。最后想了4个小时以后,只好放弃了。

    看了Analysis以后,才发现当时的想法,是有点纰漏的。就是我先入为主的认为,按照最小的连通圈进行修正是最好的方案。其次,我只考虑了存在变化的两种情况,而忽视掉了没有变化的情况。如考虑了3个变化里面的全对的1/6的概率,和对一个的3/6的概率,把剩下的两种2/6的概率给忽视了。

    这道题目换一个角度去思考,就会发现世界真的不一样。

    假设N个数组,里面全部都是没有排序好的,那么拍一次,对于数组中任意的数字,拍一次,它落回正确位置的概率为1/N。假设,拍完一次,有I个数字落回了原来的位置,那么对于没有落回原来位置的数字肯定没有落在这I个数字的位置上,如果落在了这I个数字的上面,则这I个数字肯定就是错误的,因此概率为(N-I)/N,接下来,按住I个正确的,拍一次,落回原来位置的概率为1/N-I,两者相乘的概率依然为1/N,因此一个数组正确排序的期望为整个数组中没有正确排序的数字。具体比较严格的证明,大家就看分析吧。我也只看懂了一点点,哈哈……

     代码相当简洁啊!

#include <iostream> #include<fstream> using namespace std; int cas; int index =1; int n; int result; ifstream fin("gor_large.in"); ofstream fout("gor_large.out"); void read() { fin >> n; result =0; int temp=0; for (int i=1; i <= n; i ++) { fin >> temp; if (temp != i) { result ++; }//end if }//end for } int main() { fin >> cas; while (index <= cas) { read(); fout <<"Case #"<< index <<": "<< result << ".000000" << endl; index ++; } return 0; } 

 

 

 

 

 

你可能感兴趣的:(ios,算法,Google)