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;
}

 

posted @ 2013-04-26 23:00  Titanium  阅读(283)  评论(0编辑  收藏  举报