HUST 1605 Gene recombination

隐式图搜索

训练的题目,题意:输入n表示串(串为基因,只会出现ACGT)的长度,下面两行长度为n的串,第一个为起始串,第二个为目标串。

对串能做两种操作。1.将头元素移动到尾部。2.最前面两个元素交换位置。从起始串到目标串的最少操作次数是多少,输出

这题一看,觉得是DP,后来发了两三分钟的样子想到了是搜索。对于当前的串,它是一个状态,通过两个操作,能产生两个新的状态,所以这个过程就可以建图,搜索,找出两点间的最短路。注意这里不是树,因为很容易想到,这个图是可以有环的。另外,可以大致计算到状态数是很多的(串长最大为12),所以不能显式建图,当然也没必要显式建图,因为很多点(状态)是不会去到的

很快打出代码,过sample,提交,超时。超时还是预料到的,因为没有做剪枝,会对相同状态搜索

然后就想怎么剪枝,显然是要记录那些状态被搜索过,这个是最初想到的(但为什么不写呢,反省)

注意这里的状态时一个字符串,并不好表示它,所以可以想到转化为数字,状态压缩!因为只有ACGT,所以对应为0123,那么整个串就是一个四进制数。

状态数最大可以对应到4^12的,但是悲剧的是,这题会卡内存,不能用vis数组来标记,另外,这个图,并不是全部状态都会去到。

那些怎么办呢?用vector?把搜索的状态存在vector中,每得到一个状态,就去vector里面扫描一次看看是否存在?这样依然是超时的。那么可用set?直接在set中查找这个状态?后来我没往这里想了,因为从最初我就想到了哈希(但是为什么不写呢?反省!),哈希应该是最快的。

最后就静下来写哈希,写哈希怎么写呢?状态是一个字符串,它可以变为一个四进制数,两者是完全等价的,那么是用这个四进制数去哈希好呢,还是用字符串去哈希好呢?最后选择了用字符串去哈希,使用BKDHash去做,冲突少效果好

为了再快点,在哈希的时候,是用字符串去计算映射地址的,但是地址里保存是的那个四进制数!

(另外:处理冲突,使用了静态链表,即数组模拟)

到了这里,就没什么困难了,就是搜索了,搜索时,起始状态入队,次数为0,以后每次搜索,次数加1即可

具体步骤:

1.得到一个新的字符串,在哈希表中查找它,如果哈希表中已经有了,就跳过,证明这个状态已经被搜索过,不要再搜它,就算搜它,也不是最优解,因为搜索时是按照步数从小到大来的。当前步数一定比之前的大

2.如果在哈希表中没有这个状态,那么插入哈希表中,并且这个状态要入队。

3.所以队列的元素应该记录两个信息,状态,搜到这个状态需要多少次。

3.可以知道一个状态不会两次入队,一个状态一旦入队,它的次数就是最小次数

 

另外,网上有人说,这个宽度搜索需要优先队列,这是多余的,不需要,使用优先队列的目的是为了每次出队的元素都是次数最少的,但是整个搜索的过程就是从次数递增的规则来的,所以后来入队的元素的次数一定 >= 之前的元素的次数,况且,一个状态不会二次入队

 

#include <cstdio>

#include <cstring>

#include <queue>

using namespace std;

#define N 3000000 

//这东西,最少要4,5百万,3百万都不行,会越界,存不下所有状态

//数组并不是开小点或者开大点好,因为这个数组是对应哈希的,数组的大小

//会多少影响到哈希表的冲突,测试了一下,1千万到3千万间时间较好

#define LEN 15

#define INF 0x3f3f3f3f

#define MM 0x7fffffff





int n;

char S[LEN] , T[LEN];

int st,ed;

struct State{ //压缩成数字来表示一个状态

   int s,c;

};

int head[N+10] , tot; //哈希

struct edge{

   int s,next;

}e[N+10];



typedef struct edge edge;

typedef struct State State;

queue<State>q;



void add(int index ,int s)

{

   e[tot].s=s;

   e[tot].next=head[index];

   head[index]=tot++;

}



int cton(char *str) //字符串转数字

{

   int c=1 , ans=0;

   for(int i=n-1; i>=0; i--)

   {

      int s;

      if(str[i]=='A')      s=0;

      else if(str[i]=='C') s=1;

      else if(str[i]=='G') s=2;

      else                 s=3;

      ans += s*c;

      c *= 4;

   }

   return ans;

}



void ntoc(int x , char *str) //数字转字符串

{

   for(int i=n-1; i>=0; i--)

   {

      int s=x%4;

      if(s==0)       str[i] = 'A';

      else if(s==1)  str[i] = 'C';

      else if(s==2)  str[i] = 'G';

      else           str[i] = 'T';



      x /= 4;

   }

   str[n] = '\0';

}



int BKDHash(char *str) //字符串的哈希映射用BKD

{

   int len = strlen(str);

   int seed = 131;

   int res = 0;

   for(int i=0; i<len; i++)

      res = res*seed + str[i];

   res = (res&MM)%N;

   return res;

}



int find(char *str , int s) //查找

{

   int index = BKDHash(str);

   for(int k=head[index]; k!=-1; k=e[k].next)

     if(e[k].s == s)

        return k;

   add(index,s); 

   //在哈希表中查不到这个状态,是一个新的状态,添加进哈希表中

   return -1;

}



void BFS()

{

   int res;

   int index;

   int i,j;

   char s1[LEN] , s2[LEN];

   State sta;

   while(!q.empty())

   {

      State u=q.front()  ,  v;

      ntoc(u.s,s1); q.pop();



      //第一种转换方式,头元素放到尾部

      for(i=0,j=1; j<n; i++,j++) s2[i] = s1[j];

      s2[n-1] = s1[0]; s2[n] = '\0';

      v.s=cton(s2);



      index = find(s2,v.s);

      if(index == -1) //一个新的状态

      {

         v.c = u.c+1;

         q.push(v);

         if(v.s == ed)

         { res=v.c; break; }

      }



      //第二种转换方式,两个头元素交换

      for(int i=2; i<n; i++)  s2[i] = s1[i];

      s2[0] = s1[1];  s2[1] = s1[0];  s2[n]='\0';

      v.s=cton(s2);



      index = find(s2,v.s);

      if(index == -1)

      {

         v.c=u.c+1;

         q.push(v);

         if(v.s == ed)

         { res=v.c; break; }

      }

   }

   printf("%d\n",res);

}



int main()

{

    //freopen("fuckcase.txt","r",stdin);

    //freopen("output.txt","w",stdout);

   while(scanf("%d",&n)!=EOF && n)

   {

      scanf("%s%s",S,T);

      if(!strcmp(S,T))

      { printf("0\n"); continue; }



      tot=0;

      memset(head,-1,sizeof(head));

      st=cton(S); ed=cton(T);

      int index = BKDHash(S); //字符串的哈希用BKD

      add(index,st);

      State sta;

      sta.s=st; sta.c=0;

      while(!q.empty()) q.pop(); 

      //忘记清空队列,wa了很多次,后来发现单case输入和多case输入不同,才发现

      //引以为戒

      q.push(sta);

      BFS();

   }

   return 0;

}

 

你可能感兴趣的:(com)