FanJin的博客

奋战在肿瘤学领域的程序员

poj字符串总结

1)poj1002 难度:1

题意:给你一些字母与数字的对应关系,忽略‘-’,求是否有重复的号码,若有重复则输出重复次数(按字典序输出)。

分析:先将所有字符串统一为数字串,然后字符串Qsort。

2)poj1200   Crazy Search 难度:2

题意: 找出不相同的子串数量,字母表大小和子串长度会给定.

分析:将n长的字符串转为nc进制的数字(longint能够承受),并用bool数组标记。对于出现的字符最好手动赋asc2码值。(这题我贡献了9次,poj没说清数据范围,我把问题弄复杂了)

3)poj1204  Word Puzzles 难度:4

题意:给定一个单词表,还有一批单词,要找到这些单词在单词表中的位置。单词表有 L 行,每个行都是 C 个字母,要查找的单词有 W 个,查找单词可以沿着 8 个方向进行,A 代表正北,顺时针依次是 B,C,D,E,F,G,H。

分析:暴利是过不了的,那就trie树吧!若将单词表加入树中,空间明显不够!逆向思维:将要查找的单词加入树中,

然后将输入的单词表拿到 trie 数组中搜索,如果能从根搜索到叶节点,说明这个单词存在,否则不存在。(这题好可以用AC自动机做,可惜我不会)

附代码:

const
  dirx:array[1..8] of longint
  =(-1, -1, 0, 1, 1, 1, 0, -1);
  diry:array[1..8] of longint
  =(0, 1, 1, 1, 0, -1, -1, -1);
var
  i,j,k,l,n,m,t,x,y,ii,gx,gy,q,len,count:longint;
  bool:array[0..1000] of boolean;
  link,ne,list:array[0..2000000] of longint;
  ch:array[0..2000000] of char;
  str:string;
  ans:array[1..1000,1..3] of longint;
  map:array[0..1001,0..1001] of char;

procedure build(x,k:longint);
var
  i:longint;
begin
    if k=len+1 then
    begin
      link[x]:=ii; exit;
    end;
    i:=list[x];
    while i<>0 do
    begin
      if ch[i]=str[k] then
      begin
        build(i,k+1);exit;
      end;
      i:=ne[i];
    end;
    inc(t);ne[t]:=list[x];list[x]:=t;
    ch[t]:=str[k];
    build(t,k+1);
end;

procedure find(x,i,j,k:longint);
var
  y:longint;
begin
  if (link[x]>0)and(not bool[link[x]]) then
  begin
    y:=link[x];
    inc(count);bool[y]:=true;ans[y,1]:=gx;ans[y,2]:=gy;ans[y,3]:=k;
  end;
  ii:=list[x];
  while ii<>0 do
  begin
    if map[i,j]=ch[ii] then
    find(ii,i+dirx[k],j+diry[k],k);
    ii:=ne[ii];
  end;
end;

procedure work;
begin
  for i:=1 to n do
  for j:=1 to m do
  for k:=1 to 8 do
  begin
    gx:=i-1;gy:=j-1;
    find(0,i,j,k);
    if count=q then exit;
  end;
end;

procedure print;
begin
  for i:=1 to q do
  begin
    write(ans[i,1],' ',ans[i,2],' ',char(ans[i,3]+64)); writeln;
  end;
end;

begin
  assign(input,'poj.in');reset(input);
  assign(output,'poj.out');rewrite(output);
  readln(n,m,q);
  for i:=1 to n do
  begin
    for j:=1 to m do read(map[i,j]);
    readln;
  end;
  for ii:=1 to q do
  begin
    readln(str);len:=length(str);
    build(0,1);
  end;
  work;
  print;
  close(input);close(output);
end.               


4)poj1229 Wild Domains 难度:5

题意:模糊匹配。给你两个字符串,每个串有几个域,每个域用‘.’隔开。有些域名模糊用‘*’、‘?’、‘!’代替。

‘*’功能:代替1个或1个以上域名;

‘?’功能:代替1至3个域名;

‘!’功能:代替至少3个域名。

要你判断给定的两个串能否匹配。

分析:dp题,神奇的替换:

ONE  匹配一个单词 (用1表示)

ZERO_ONE 匹配零个或一个单词(用0表示)

ANY 可以匹配零个或多个单词(用2表示)

其他:即非通配符的用>2的整数表示

这样:

* = ONE,ANY
? = ONE, ZERO_ONE, ZERO_ONE
! = ONE, ONE, ONE, ANY

替换后即可dp。

F[a,b]表示p1串匹配到了a位置,p2串匹配到了b位置,是否匹配成功。

转移:

Ifp1[a]=0 {

If p2[b]=0 then f[a,b]:=f[a-1,b] or f[a,b-1] orf[a-1,b-1];

If (p2[b]=1)or(p2[b]>2) thenf[a,b]:=f[a-1,b] or f[a-1,b-1];

If p2[b]=2 then f[a,b]:=f[a-1,b] or f[a,b-1] orf[a-1,b-1];}

Ifp1[a]=1 {

If p2[b]=0 then f[a,b]:=f[a,b-1] or f[a-1,b-1];

If (p2[b]=1)or(p2[b]>2) thenf[a,b]:=f[a-1,b-1];

If p2[b]=2 then f[a,b]:=f[a-1,b] or f[a,b-1] orf[a-1,b-1];}

Ifp1[a]=2 then f[a,b]:=f[a-1,b] or f[a,b-1] or f[a-1,b-1];

Ifp1[a]>2 {

If p2[b]=0 then f[a,b]:=f[a,b-1] or f[a-1,b-1];

If p2[b]=1 then f[a,b]:=f[a-1,b-1];

If p2[b]=2 then f[a,b]:=f[a-1,b] or f[a,b-1] orf[a-1,b-1];

If (p2[b]>2)and(st[p1[a]]=st[p2[b]])thenf[a,b]:=f[a-1,b-1]}

附代码:

var
  i,j,k,l,m,n,t,tt:longint;
  s1:string;
  p1,p2:array[1..20000] of longint;
  st:array[1..700] of string;
  flag:boolean;
  f:array[0..3000,0..3000] of boolean;

procedure work;
begin
  l:=length(s1);i:=1;n:=0;
  while i<=l do
  begin
    j:=i; flag:=false;
    while (s1[j]<>'.')and(j<l) do inc(j);
    if i=l then inc(j);
    if i+1=j then
    begin
      if s1[i]='*' then   //p=0=01 p=1=1 p=2=any
      begin
        inc(n);p1[n]:=1;
        inc(n);p1[n]:=2;
        flag:=true;
      end
      else if s1[i]='?' then
      begin
        inc(n);p1[n]:=1;
        inc(n);p1[n]:=0;
        inc(n);p1[n]:=0;
        flag:=true;
      end
      else if s1[i]='!' then
      begin
        for k:=1 to 3 do
        begin
          inc(n);p1[n]:=1;
        end;
        inc(n);p1[n]:=2;
        flag:=true;
      end;
    end;
    if not flag then
    begin
      inc(tt);
      st[tt]:=copy(s1,i,j-i);
      inc(n);p1[n]:=tt;
    end;
    i:=j+1;
  end;
end;

procedure init;
begin
  tt:=2;
  readln(s1);
  work;
  p2:=p1;
  m:=n;
  readln(s1);
  work;
end;

procedure dp;
begin
  f[0,0]:=true;
  for i:=1 to n do
  if (p1[i]=0)or(p1[i]=2) then
  f[i,0]:=true
  else break;
  for i:=1 to m do
  if (p2[i]=0)or(p2[i]=2) then
  f[0,i]:=true
  else break;
  for i:=1 to n do
  for j:=1 to m do
  begin
    if p1[i]=0 then
    begin
      f[i,j]:=f[i-1,j] or f[i-1,j-1];
      if (p2[j]=0)or(p2[j]=2) then f[i,j]:=f[i,j] or f[i,j-1];
    end
    else if p1[i]=1 then
    begin
      f[i,j]:=f[i-1,j-1];
      if p2[j]=0 then f[i,j]:=f[i,j] or f[i,j-1];
      if p2[j]=2 then f[i,j]:=f[i,j] or f[i,j-1] or f[i-1,j];
    end
    else if p1[i]=2 then
    begin
      f[i,j]:=f[i-1,j] or f[i,j-1] or f[i-1,j-1];
    end
    else begin
      if p2[j]<=2 then
      begin
        f[i,j]:=f[i-1,j-1];
        if p2[j]=0 then f[i,j]:=f[i,j] or f[i,j-1];
        if p2[j]=2 then f[i,j]:=f[i,j] or f[i,j-1] or f[i-1,j];
      end
      else if st[p1[i]]=st[p2[j]] then f[i,j]:=f[i-1,j-1];
    end;
  end;
  if f[n,m] then writeln('YES') else writeln('NO');
end;

begin
  assign(input,'poj.in');reset(input);
  assign(output,'poj.out');rewrite(output);
  readln(t);
  while t>0 do
  begin
    fillchar(f,sizeof(f),0);
    dec(t);
    init;
    dp;
  end;
  close(input);close(output);
end.                       


5)poj1816  WildWords  难度:4

题意:给定n个模式串,m个匹配串,对于m个匹配串,问n中哪些匹配串可以匹配上。

分析:对模式串进行建树,注意有重复的模式串!匹配时,遇到匹配串当前字符等于trie中的当前字符或trie中的当前字符为‘*’或‘?’时将继续进行下一层的匹配,另外当当前trie中的字符为‘*’时,有两种例外操作:

直接跳过‘*’和直接跳过当前匹配串中字符。

详可参考代码:

var
  i,j,k,x,n,m,t,ii,len:longint;
  link,ne,list:array[0..600000] of longint;
  next:array[0..100000] of longint;
  ch:array[0..100000] of char;
  g:array[0..100] of longint;
  ans:array[0..100000] of boolean;
  str:string[30]; flag:boolean;
procedure build(x,k:longint);
var
  i:longint;
begin
  if k=len+1 then
  begin
    if link[x]<>0 then next[ii]:=link[x];
    link[x]:=ii; exit;
  end;
  i:=list[x];
  while i<>0 do
  begin
    if ch[i]=str[k] then
    begin
      build(i,k+1);exit;
    end;
    i:=ne[i];
  end;
  inc(t);ne[t]:=list[x];list[x]:=t;ch[t]:=str[k];build(t,k+1);
end;

procedure find(x,k:longint);
var
  i:longint;
begin
  if k=len+1 then
  begin
    i:=list[x];
    while i<>0 do
    begin
      if ch[i]='*' then find(i,k);
      i:=ne[i];
    end;
    i:=link[x];
    while i<>0 do
    begin
      flag:=true;
      ans[i]:=true;
      i:=next[i];
    end;
    exit;
  end;
  if ch[x]='*' then find(x,k+1);
  i:=list[x];
  while i<>0 do
  begin
    if (ch[i]=str[k])or(ch[i]='*')or(ch[i]='?') then find(i,k+1);
    if ch[i]='*' then find(i,k);
    i:=ne[i];
  end;
end;

begin
  assign(input,'poj.in');reset(input);
  assign(output,'poj.out');rewrite(output);
  readln(n,m);
  for ii:=1 to n do
  begin
    readln(str);len:=length(str);
    build(0,1);
  end;
  for ii:=1 to m do
  begin
    readln(str); len:=length(str);flag:=false;  fillchar(ans,sizeof(ans),0);
    find(0,1); if not flag then write('Not match')
    else for i:=1 to n do if ans[i] then write(i-1,' '); writeln;
  end;
  close(input);close(output);
end.                           


6)poj2185  MilkingGrid  难度:4

题意:给你一个字符矩阵,求出它的最小覆盖子矩阵,即使得这个子矩阵的无限复制扩张之后的矩阵,能包含原来的矩阵。 即二维的最小覆盖子串。

分析:KMP很好的一道题。首先易证:最小覆盖子矩阵一定靠左上角。那么,我们考虑求出每一行的最小重复串长度,所有行的最小重复串的长度的lcm就是最小重复子矩阵的宽。然后我们对列也做相同的操作。于是我们就可以求得最小重复子矩阵的大小了。(这里要注意一点:当所得的宽大于原来的宽时,就让等于原来的宽,长也如此)。算法实现:算法的核心在于高效的求出每一行和每一列的最小重复串,这个可以最原串做一次KMP中的get_next(有的资料叫get_pre,其实都是一样的)。

附程序:

var
  i,j,n,m,ansc,ansr,len:longint;
  next:array[0..10001] of longint;
  map:array[0..10001] of string;
  st:string;
function nextc(x:longint):longint;
var
  i,j:longint;
begin
  next[1]:=0;j:=0;
  for i:=2 to m do
  begin
    while (j>0)and(map[x,j+1]<>map[x,i]) do j:=next[j];
    if map[x,i]=map[x,j+1] then inc(j);
    next[i]:=j;
  end;
  exit(m-next[m]);
end;

function nextr(x:longint):longint;
var
  i,j:longint;
begin
  next[1]:=0;j:=0;
  for i:=2 to n do
  begin
    while (j>0)and(map[j+1,x]<>map[i,x]) do j:=next[j];
    if map[i,x]=map[j+1,x] then inc(j);
    next[i]:=j;
  end;
  exit(n-next[n]);
end;
function gcd(a,b:longint):longint;
begin
  if b=0 then exit(a);
  gcd:=gcd(b,a mod b);
end;

function lcm(a,b:longint):longint;
begin
  exit(a*b div gcd(a,b));
end;

begin
  assign(input,'poj.in');reset(input);
  assign(output,'poj.out');rewrite(output);
  readln(n,m); ansc:=1;ansr:=1;
  for i:=1 to n do readln(map[i]);
  for i:=1 to n do
  begin
    ansc:=lcm(ansc,nextc(i));
    if ansc>m then
    begin
      ansc:=m;break;
    end;
  end;
  fillchar(next,sizeof(next),0);
  for i:=1 to m do
  begin
    ansr:=lcm(ansr,nextr(i));
    if ansr>n then
    begin
      ansr:=n;break;
    end;
  end;
  writeln(ansc*ansr);
  close(input);close(output);
end.                                  

7)poj2513  ColoredSticks 难度:3

题意:给定一些木棒,木棒两端都涂上颜色,求是否能将木棒首尾相接,连成一条直线,要求不同木棒相接的一边必须是相同颜色的。

分析:可以用图论中欧拉路的知识来解这道题,首先可以把木棒两端看成节点,把木棒看成边,这样相同的颜色就是同一个节点,于是问题就转化为了欧拉回路问题。

由图论知识可以知道,无向图存在欧拉路的充要条件为:

①    图是连通的;

②    所有节点的度为偶数,或者有且只有两个度为奇数的节点。

图的连通性可以用并查集判断,而此处节点是字符串,并查集不方便实现,需要对字符串编号,而对于字符串的查找及统计可用trie树(较快)或hash(较慢,需尽量使冲突最少)。

trie树简单,此处不讲;此题hash函数较好的为:将字符串转为26进制数(因为此题字符串较短),然后mod一个大质数。

8)poj1699  BestSequence 难度:4

题意:现在我们知道基因由脱氧核苷酸组成。组成脱氧核苷酸的碱基有A(adenine), C(cytosine), G(guanine), T(thymine)四种。现在给出几个基因片段,要求你将它们排列成一个最短的序列,序列中使用了所有的基因片段,而且不能翻转基因。

分析:深度搜索,剪枝。求最短父序列。把数据读入之后,先计算字符串m接在字符串n前面总字符串长度将增加多少,保存在addlen[m][n]中。例如BCAT 接在 ATCG 前面,则增加的长度为2,然后就用addlen[][]数组进行搜索,使用used[]数组标记本次深度搜索该字符串是否被使过,保证每次深度搜索都能使每个字符串用到且仅使用一次。当深度达到字符串总数n时,表明所有的字符串都接在一起了,比较当前长度与之前最短长度的值,保留最短长度。{这题思路是在网上copy的,我原先没有预处理,还带了当前字符串后缀(事实上无需这样,看了网上的题解,大悟)一起dfs,果断TLE了,预处理好啊!}{然后这题要注意的是:题目说必须每个基因片段都要用到,则不允许有包含关系的拼凑方式,这里我WA了好多次}

这题还可以用状压DP写。其实思路和上面一样,只是将DFS改成了DP,效率提高了些。二进制串s压缩了当前状态,已处理串在二进制串s中记为1,否则为0;状态f[s,i]表示以处理情况为s,最后一个串是i串时的最小长度。

9)poj3080  BlueJeans 难度:2

题意:给你n个长度都为60的串,求出它们的最长公共子串,如果这个子串的长度 < 3则输出“nosignificant commonalities”,如果存在多个最长公共子串,则输出字典序最小的那一个。

分析:枚举+KMP。枚举第一个串的所有后缀串(而不是枚举第一个串的所有子串),每一个后缀串做一次KMP,求出这些串的与其余所有串的最长匹配。

另外要注意某些细节问题,例如字符串相当于字符数组,不要妄想一个未清空的字符串已清空,即便watch到已清空,我在这里WA了几次。

10)poj3630  PhoneList 难度:2

题意:给定 N 个电话号码,问是否有一个串是另一个的前缀。

分析:先Qsort,然后可以暴力做,或者用字母树(字母树慢一点,因为空间较大……)。

11)poj3690  Constellations 难度:未知

题意:给出天空中星星的一幅图,*表示星星,0表示无,二维的。再给出一些星座的形状。问有多少个星座出现在天空中,一种星座只出现一次。

此题没有AC,交了好几次都TLE了,网上某牛人用了暴力方法:字符串压缩+kmp,复杂度为n*m*t,我用pascal编了一个,还是TLE,他C++竟然AC了?!

 该牛地址:

http://hi.baidu.com/%C1%D9%CA%B1%B1%B8%D3%C3%D5%CB%BA%C5/blog/item/8cd7b9df5d7a57aecc116613.html

 

做题总结:

Poj上的字符串题暂时就做了这11道,网上提供的其他题都较难,鉴于NOIP不考拓展KMP、自动机、后缀数组之类的,对于用到这些高级方法的题目我都没做。

通过做这些题,进一步熟悉了KMP和字母树,对于字符串类型的题目有了一定的了解:

第一类:排序类型。排序,对于处理有重复字符串或字符串有包含关系(前缀关系)的题目有一定的作用。

第二类:hash类型。Hash有助于字符串查找,适用于有包含关系的题。字符串hash函数的一般写法:字符串较短时(长度<=10),将字符串转成X进制数(x一般等于26),然后mod一个大质数;字符串较长时,hash字符串前几位和后几位即可(有时可以将长度也哈进去)。

第三类:trie类型。和hash作用类似,但查找效率更高。更适用于规模较大的或较活跃的字符串查找,在字符串类型题目中比hash更实用。

第四类:dp类型。主要是线性DP和状压DP,叫经典的有“模糊匹配”。

第五类:kmp类型。用于字符串匹配题目。其中kmp的next数组(有资料称prefix数组,本质一样)较为实用,还可以用来求最小覆盖字串。

第六类:综合型。如poj2513,集图论、并查集、trie(或hash)于一身。


参考资料:

某犇博客:http://hi.baidu.com/zfy0701/blog/item/440e923e1bc4183870cf6c89.html

及“依然”、“小优”等各大牛博客。

感谢poj。



阅读更多
文章标签: string pascal poj
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭