hujunhua 发表于 2019-10-30 17:29:16

忘记了我居然在这个帖子下面回复了这么多。:o

mathe 发表于 2019-11-1 08:48:40

试着测试一下随机mathe
代码假设Fans给出的目标数据就是按0,1,2,...,N-1顺序给出(但是mathe不知道)
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <vector>

#ifndef N
#define N 20
#endif

#define TC 8

#define TARGET 1

struct Data{
    char v;
    int c;
};

std::vector<Data> usedlist;
std::vector<Data> leftlist;
char leftc;
char avail;
double tvalue;
double ve;
void init()
{
    tvalue = log(2*M_PI*N)/2+N*(log(N)-1)-log(TARGET);
    ve=exp(-1.0);
    int i,j;
    leftc=N;
    for(i=0;i<N;i++){
       for(j=0;j<N;j++)avail=j;
    }
}

void dumpleft()
{
    int i;
    int j;
    for(i=0;i<leftlist.size();i++){
       printf("\n");
       for(j=0;j<N;j++)printf("%d ",leftlist.v);
    }
    printf("\n");
}

char used;
char mc;
char umc;
Data tryv;
int depth;
void search()
{
    if(depth>=N){
      leftlist.push_back(tryv);
      return;
    }
    int i,j;
    for(i=0;i<usedlist.size();i++){
      if(mc>usedlist.c||umc>N-usedlist.c)return;
    }
    for(i=0;i<leftc;i++){
      int k = avail;
      if(used==0){//The value has not been used, try use it
            used=1; tryv.v=k;if(k==depth)tryv.c++;
            for(j=0;j<usedlist.size();j++){
                if(usedlist.v==k){
                  mc++;
                }else{
                  umc++;
                }
            }
            depth++;
            search();
            depth--;
            for(j=0;j<usedlist.size();j++){
                if(usedlist.v==k){
                  mc--;
                }else{
                  umc--;
                }
            }
            used=0;if(k==depth)tryv.c--;
      }
    }
}

void collect_lefts()
{
    depth=0;
    tryv.c=0;
    memset(used,0,sizeof(used));
    memset(mc,0,sizeof(mc));
    memset(umc,0,sizeof(umc));
    search();
}

int trygenrand(Data& d)
{
    char cused;
    memset(cused,0,sizeof(cused));
    int i,j,k;
    d.c=0;
    for(i=0;i<N;i++){
      j=rand()%leftc;
      int choice=-1;
      for(k=j;k<leftc;k++){
         if(cused]==0){choice=k;break;}
      }
      if(choice<0){
         for(k=0;k<j;k++){
            if(cused]==0){choice=k;break;}
         }
      }
      if(choice<0)return -1;
      cused]=1;
      d.v=avail;
      if(d.v==i)d.c++;
    }
    return 0;
}

void genrand(Data& d)
{
    while(trygenrand(d)<0);
    int i,j;
    usedlist.push_back(d);
    if(d.c==0){
       for(i=0;i<N;i++){
         for(j=0;j<leftc;j++){
               if(d.v==avail)break;
         }
         if(j>=leftc){
               fprintf(stderr,"Internal error\n");
               exit(-1);
         }
         for(;j<leftc-1;j++){
               avail=avail;
         }
       }
       leftc--;
    }
    double dd=ve;for(i=0;i<d.c;i++)dd/=(i+1);
    tvalue+=log(dd);
}


int main()
{
    Data d;
    srand(time(NULL));
    init();
    int t=0;
    while(leftc>TC){
      genrand(d);
      t++;
    }
    printf("try %d times to reach small probability, leftc = %d\n", t, leftc);
    collect_lefts();
    printf("Total %d left\n", (int)leftlist.size());
    dumpleft();
}

测试了含20个数字,而mathe总是瞎猜直到听到12次Fans说你全错了位置在统计余下的可能
每次统计可能会很快(不到一秒)也可能会花费数分钟
试验结果
第一次:
try 72 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
第二次:
try 57 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

第三次:
try 53 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

第四次:
try 48 times to reach small probability, leftc = 8
Total 5 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
5 1 12 0 4 19 6 8 17 9 10 3 15 13 14 2 16 7 18 11
5 1 17 0 4 19 6 8 3 9 10 12 15 13 14 2 16 7 18 11
5 1 17 0 14 4 6 8 3 9 11 12 15 13 10 2 16 7 18 19
9 17 12 8 14 0 6 15 4 5 11 3 19 1 10 2 16 7 18 13
于是我们需要补猜一次
5 1 17 * 4 **************
就可以完全区分了。

第五次:
try 43 times to reach small probability, leftc = 8
Total 2 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 13 10 11 12 9 14 15 16 17 18 19

第六次:
try 42 times to reach small probability, leftc = 8
Total 2 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9 19 11 12 13 14 15 16 17 18 10

第七次:
try 56 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

第八次:
try 70 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

第九次:
try 61 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

第十次
try 51 times to reach small probability, leftc = 8
Total 1 left

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

波动还是挺大的。
另外看来随机测试的次数还可以减少,提前分析规律会有好处。只是需要计算力非常强大,mathe远远不够。

mathe 发表于 2019-11-1 16:15:36

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <vector>

#ifndef N
#define N 12
#endif

#define MINTC 3
#define TC 6

#define TARGET 5

struct Data{
    char v;
    int c;
    bool operator==(const Data& d)const{
      int i; for(i=0;i<N;i++)if(v!=d.v)return false;
      return true;
    }
};

std::vector<Data> usedlist;
std::vector<Data> leftlist;
char leftc;
char avail;
double tvalue;
double ve;
void init()
{
    tvalue = log(2*M_PI*N)/2+N*(log(N)-1)-log(TARGET);
    ve=exp(-1.0);
    int i,j;
    leftc=N;
    for(i=0;i<N;i++){
       for(j=0;j<N;j++)avail=j;
    }
}

void filter(const Data& d)
{
    int i,j;
    std::vector<bool> valid;
    valid.resize(leftlist.size());
    for(i=0;i<valid.size();i++)valid=true;
    for(i=0;i<leftlist.size();i++){
      int c=0;
      for(j=0;j<N;j++)if(d.v==leftlist.v)c++;
      if(c!=d.c)valid=false;
    }
    for(i=0,j=0;i<leftlist.size();i++){
      if(valid){
         if(i>j)leftlist=leftlist;
         j++;
      }
    }
    leftlist.resize(j);
}

void getMaxDist(Data& d)
{
    int i,j;
    int c;
    int used;d.c=0;
    for(i=0;i<N;i++)used=0;
    for(i=0;i<N;i++){
      memset(c,0,sizeof(c));
      for(j=0;j<leftlist.size();j++){
             c.v]++;
      }
      int short_dist = 2*N+1, select=-1;
      for(j=0;j<N;j++){
             int v=2*c-leftlist.size();
             if(v<0)v=-v;
             if(v<short_dist&&used==0){
               short_dist=v;select=j;
             }
      }
      if(select>=0){
            used=1; d.v=select;if(i==select)d.c++;
      }else d.v=-1;
    }
    for(i=0,j=0;i<N;i++){
      if(d.v<0){
            for(;j<N;j++){
                if(used==0){
                   used=1;d.v=j;if(i==j)d.c++;break;
                }
            }
      }
    }
    do{
       for(i=0;i<usedlist.size();i++){
         if(d==usedlist){
               break;
         }
       }
       if(i==usedlist.size())return;
       //If it has been used, randomly swap any two items
       int s=rand()%N; int t=rand()%(N-1); if(t>=s)t++;
       int tmp=d.v;d.v=d.v;d.v=tmp;d.c=0;
       for(i=0;i<N;i++)if(d.v==i)d.c++;
    }while(1);
}

void output(const Data& d)
{
    int i;
    for(i=0;i<N;i++){
      printf("%d ",d.v);
    }
    printf("\n");
}

void dumpleft()
{
    int i;
    int j;
    for(i=0;i<leftlist.size();i++){
       printf("\n");
       for(j=0;j<N;j++)printf("%d ",leftlist.v);
    }
    printf("\n");
}

char used;
char mc;
char umc;
Data tryv;
int depth;
int countlimit;
int search()
{
    if(depth>=N){
      leftlist.push_back(tryv);
      if(leftlist.size()>countlimit)
            return -1;
      else return 0;
    }
    int i,j;
    for(i=0;i<usedlist.size();i++){
      if(mc>usedlist.c||umc>N-usedlist.c)return 0;
    }
    for(i=0;i<leftc;i++){
      int k = avail;
      if(used==0){//The value has not been used, try use it
            used=1; tryv.v=k;if(k==depth)tryv.c++;
            for(j=0;j<usedlist.size();j++){
                if(usedlist.v==k){
                  mc++;
                }else{
                  umc++;
                }
            }
            depth++;
            if(search()<0)return -1;
            depth--;
            for(j=0;j<usedlist.size();j++){
                if(usedlist.v==k){
                  mc--;
                }else{
                  umc--;
                }
            }
            used=0;if(k==depth)tryv.c--;
      }
    }
    return 0;
}

int collect_lefts(int limit)
{
    depth=0;
    tryv.c=0;
    memset(used,0,sizeof(used));
    memset(mc,0,sizeof(mc));
    memset(umc,0,sizeof(umc));
    leftlist.clear();
    countlimit=limit;
    return search();
}

int trygenrand(Data& d)
{
    char cused;
    memset(cused,0,sizeof(cused));
    int i,j,k;
    d.c=0;
    for(i=0;i<N;i++){
      j=rand()%leftc;
      int choice=-1;
      for(k=j;k<leftc;k++){
         if(cused]==0){choice=k;break;}
      }
      if(choice<0){
         for(k=0;k<j;k++){
            if(cused]==0){choice=k;break;}
         }
      }
      if(choice<0)return -1;
      cused]=1;
      d.v=avail;
      if(d.v==i)d.c++;
    }
    return 0;
}

void genrand(Data& d)
{
    while(trygenrand(d)<0);
    int i,j;
    usedlist.push_back(d);
    if(d.c==0){
       for(i=0;i<N;i++){
         for(j=0;j<leftc;j++){
               if(d.v==avail)break;
         }
         if(j>=leftc){
               fprintf(stderr,"Internal error\n");
               exit(-1);
         }
         for(;j<leftc-1;j++){
               avail=avail;
         }
       }
       leftc--;
    }
    double dd=ve;for(i=0;i<d.c;i++)dd/=(i+1);
    tvalue+=log(dd);
}


int main()
{
    Data d;
    srand(time(NULL));
    init();
    int t=0;
    while(t<N*N){
      genrand(d);
      t++;
      if(t>=MINTC){
         if(collect_lefts(100)>=0)break;
      }
    }
    printf("try %d times to reach small probability, leftc = %d\n", t, leftc);
    if(t>=N*N)
       collect_lefts(100);
    printf("Total %d left\n", (int)leftlist.size());
    dumpleft();
    while(leftlist.size()>1){
      getMaxDist(d);
      printf("Use: ");output(d);
      usedlist.push_back(d);
      filter(d);t++;
    }
    printf("Total tried %d times\n",t);
}

试验了上面的代码,先随机,减少到一定程度,就用贪心法选择看上去还行的方法,实验结果基本上在2N次以内解决。但是N不能太大,不然程序运行时间会很长。其中参数TC建议选择接近N/2,越小代码跑得越快,但是前面随机使用次数会越多。而参数MINTC表示前面这么多次必然全随机,不会探测是否需要切换到贪心法
页: 1 2 3 4 5 [6]
查看完整版本: 猜排列问题