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(j0 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。