欧冠抽签

针对欧冠16进8的抽签过程,我主要有以下几个问题:

1,一共有多少种可能的对阵?

2,两次抽签抽出相同的对阵概率有多大(多小)?

3,每种单场对阵(比方说米兰vs巴萨,皇马vs曼联)出现的概率有多少?

4,到底有没有黑幕?


先解决多少种对阵的问题。基本思路是八皇后。不同的是验证位置是否合法有效的方法。八皇后是要避免竖斜方向,抽签要避免同小组,同足协,和竖向(抽了同一支球队)。好比在一个破棋盘上摆“车”。如图就是今年的抽签结果。

画图画的,见谅。车迷不哭么么哒。

代码如下。八皇后+位运算

#define PAIR 8 //16 teams
#define FUAI 0
 
enum association{ITA,ESP,ENG,GER,FRA,POR,SCO,UKR,TUR}; //football associations
 
static const enum association as_st_1[PAIR] = {FRA,GER,ESP,GER,ITA,GER,ESP,ENG}; //standing 1, group A to H
static const enum association as_st_2[PAIR] = {POR,ENG,ITA,ESP,UKR,ESP,SCO,TUR}; //standing 2, group A to H
 
static char versus[PAIR] = {0};
static int count = 0; //# of possible results
 
int isvalid(int round, char pos){//check if the pos(ition) is valid
    if((round!=pos)&&(as_st_2[round]!=as_st_1[pos])){
        int i;
        char c = 0;
        for(i=0;i<round;i++){
            c = c|versus[i];
        }
        return (c!=(c|(1<<(PAIR-1-pos))));
    }
    return 0;
}
 
void draw_once(int round){
    int pos;
    for(pos=0;pos<PAIR;pos++){
        if(isvalid(round,pos)){
            versus[round] = (0|(1<<(PAIR-1-pos)));
            if(round == PAIR-1){
                count++;
                versus[round]=0;
                return;
            }
            draw_once(round+1);
            versus[round]=0;
        }
    }
}
 
void draw(){ //封装用的
    draw_once(0);
}
 
void print_available_versus(){
    int j;
    char k;
    for(j=0;j<PAIR;j++){
        for(k=0;k<PAIR;k++){
            if(isvalid(j,k)!=0){
                printf("YES\t");
            }else{
                printf("NO\t");
            }
        }
        printf("\n");
    }
}
 
void main(){
    if(FUAI){
        printf("Milan vs Barca, RM vs ManU, qita suibian!\n");
    }else{
        print_available_versus();
        draw();
        printf("%d\n",count);
    }
}

P.S. 我是不会告诉你们代码里有彩蛋的。不信你把第二行的0换成1试试。

gcc 4.6.1跑结果如下:

第一个8*8矩阵是抽签前所有可能的单场对阵。从上到下第1到8行分别代表A-H组的8个小组第二,从左到右是8个小组第一。蛋疼的同学可以验证,欧足联官网欢迎您。

5463就是所有可能的对阵了。所以你想欧组联能彩排和正式抽出一样的结果,这是什么人品。


再算每一场出现的概率。

每一场出现的概率我们可以通过累加5463种抽签对阵,统计其中单场出现的次数,除以5463得出。在这里,为了增加难度,我准备研究一下全部的抽签过程。

如果对比真实的抽签过程,前面这段代码是按照小组第二从A到H的顺序来的。真实的抽签过程可以参考(屌丝的节目 误)球迷的节日足球之夜节目转播的抽签过程(这儿下的)。

过程是这样的。

老熟人光头男因凡帝诺负责抽小组第二。给丫一个特写。

前利物浦和皇马巨星(以下简称巨星,麦克马纳曼太长了)负责抽小组第一,就是下图左一笑得很开心的那个,右一是(个小喽罗 误)欧组联竞赛总监马切蒂(according to 女主持人)。

具体过程是,光头男先抽出一个小组第二,然后马切蒂找出可能的小组第一,(巨星说快到我碗里来 误)放到巨星面前的碗里,再由巨星来抽出一个。直到全部八场对阵产生为止。

我准备先模拟全部出抽小组第二的过程,再模拟出所有可能对阵。

全部可能的小组第二抽签顺序这种问题让人菊花一紧,没错,就是您的老朋友面试的好基友全排列算法。好了少年你可以去google代码了。下面的是我(google的 误)写的(其实也还是google了一下的)。用的递归+邻位对换。

代码:

#define PAIR 8 //16 teams

enum association{ITA,ESP,ENG,GER,FRA,POR,SCO,UKR,TUR}; //football associations

static const enum association as_st_1[PAIR] = {FRA,GER,ESP,GER,ITA,GER,ESP,ENG}; //standing 1, group A to H
static const enum association as_st_2[PAIR] = {POR,ENG,ITA,ESP,UKR,ESP,SCO,TUR}; //standing 2, group A to H

static char versus[PAIR] = {0};
static int count = 0; //# of possible results

//static int st_2[PAIR] = {0,1,2,3,4,5,6,7};
static int st_2[PAIR] = {7,6,1,4,2,3,5,0}; //this year order
static int acc[PAIR][PAIR] = {0};

void swap(int* a, int* b){
    int temp = *a;
    *a = *b;
    *b = temp;
    return;
}

int isvalid(int round, char pos){//check if the pos(ition) is valid
    char c = 0;
    if((round!=pos)&&(as_st_2[round]!=as_st_1[pos])){
        int i;
        for(i=0;i<PAIR;i++){
            c = c|versus[i];
        }
        return (c!=(c|(1<<(PAIR-1-pos))));
    }
    return 0;
}

void acc_result(){
    int i,j;
    for(i=0;i<PAIR;i++){
        for(j=0;j<PAIR;j++){
            acc[i][j] += ((versus[i]>>(PAIR-1-j))&1);
        }
    }
}

void draw_once(int round){
    int pos;
    int team = st_2[round];
    for(pos=0;pos<PAIR;pos++){
        if(isvalid(team,pos)){
            versus[team] = (0|(1<<(PAIR-1-pos)));
            if(round == PAIR-1){
                count++;
                acc_result();
                versus[team]=0;
                return;
            }
            draw_once(round+1);
            versus[team]=0;
        }
    }
}

void draw(){
    draw_once(0);
}

void print_perm(){
    int i;
    for(i=0;i<PAIR;i++){
        printf("%d\t",st_2[i]);
    }
    printf("\n");
}

void perm(int m, int n){
    if(m>n){
        draw();
        return;
    }else{
        int i;
        for(i=m;i<=n;i++){
            swap(&st_2[i],&st_2[m]);
            perm(m+1, n);
            swap(&st_2[i],&st_2[m]);
        }
    }
}

void perm_draw(){ //封装
    perm(0,PAIR-1);
}

void print_available_versus(){
    int j;
    char k;
    for(j=0;j<PAIR;j++){
        for(k=0;k<PAIR;k++){
            if(isvalid(j,k)!=0){
                printf("YES\t");
            }else{
                printf("NO\t");
            }
        }
        printf("\n");
    }
}

void print_acc(){
    int i,j;
    int sum;
    for(i=0;i<PAIR;i++){
        sum = 0;
        for(j=0;j<PAIR;j++){
            sum += acc[i][j];
        }
        for(j=0;j<PAIR;j++){
            printf("%d\t",acc[i][j]);
        }
    printf("\n");
    }
}

void print_acc_per(){
    int i,j;
    int sum;
    for(i=0;i<PAIR;i++){
        sum = 0;
        for(j=0;j<PAIR;j++){
            sum += acc[i][j];
        }
        for(j=0;j<PAIR;j++){
            printf("%2.1f\t",((float)acc[i][j]/sum)*100);
        }
    printf("\n");
    }
}

void main(){
    printf("\n");
    print_available_versus();
    perm_draw();
    //draw();
    printf("%d\n\n",count);
    print_acc();
    printf("\n");
    print_acc_per();
}


P.S.这里面把彩蛋去掉了。刚才给彩蛋好评的亲请自行DIY。

最后的main里面,可以注释掉perm_draw()那一行,跑draw(),这样就能看到所有对阵里的单场对阵出现的次数和概率。比方说我跑的:

看到华丽的23.2米屌们哭了没?皇马概率最高的是抽尤文,21.8,能抽到21.5的曼联已经是人品了(0.3%的人品也是人品)。

如果少年您真心蛋疼,请注释掉draw(),跑perm_draw()吧。比方说我就蛋疼了一次:

其实概率是一样的,只是遍历了一下2亿多次不同的抽签过程而已。

至于有没有黑幕这种事,就见仁见智了。

好了今天就先到这里了我们下次节目再见。

 

P.S. 应该还有很多种做法,比方说固定某一场算剩下的可能对阵(ie.能不能把16层递归搞成8层),或者Monte Carlo Method(100万应该够收敛了吧,起码比2亿要快),etc。有什么其他想法欢迎来搞。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值