POJ2146 Confusing Login Names [最小字符串编辑距离]
给出一个字符串S1和S2,可以对S1进行添加字符(insert),删除字符(delete),替换字符(replace),交换相邻字符(swap)四种操作,问是否能在dis步骤内将S1转换成S2。
用d[i][j]表示将S1[1..i]转化为S2[1..j]的最小步数。
这几种操作可以表示为:添加d[i][j]=d[i][j-1]+1,删除d[i][j]=d[i-1][j]+1,替换d[i][j]=d[i-1][j-1]+1,交换d[i][j]=d[i-2][j-2]+1(条件为s1[i-1]==s2[j]&s1[i]==s2[j-1])。
但是仅考虑到这些还是过不了这道题的,因为交换操作可以减少其它操作的步数,比如说S1="ca",S2="abc",可以通过先swap(c,a),然后在中间insert(b),这样2步就可以由S1转化到S2,也就是说d[i][j]=d[i-2][j-3]+2。当S1="abc",S2="ca"时,可以先delete(b),然后再swap(a,c)变成2,也就是说d[i][j]=d[i-3][j-2]+2。实质是通过insert和delete辅助swap完成了不相邻的两个数的交换。这题中的dis范围是2,最多也只能操作两次,一开始没注意这个条件,还想了好久。。。
最后的DP方程大概就是,具体的条件看代码就清楚了。
#include <cstdio> #include <string.h> #include <algorithm> #define INF 0x3fffffff using namespace std; struct cstr{ char s[22]; bool operator <(const cstr& c)const{ return strcmp(s,c.s)<0; } }st[205]; int n,dis,d[20][20]; int dp(char *s1,char *s2){ int l1=strlen(s1),l2=strlen(s2); memset(d,0,sizeof d); for(int i=1;i<=l2;i++)d[0][i]=i; for(int i=1;i<=l1;i++)d[i][0]=i; for(int i=1;i<=l1;i++){ for(int j=1;j<=l2;j++){ d[i][j]=INF; if(s1[i-1]==s2[j-1]){ d[i][j]=d[i-1][j-1]; }else{ d[i][j]=min(d[i][j-1],min(d[i-1][j],d[i-1][j-1]))+1; } if(i>=2&&j>=2&&s1[i-2]==s2[j-1]&&s1[i-1]==s2[j-2]){ d[i][j]=min(d[i][j],d[i-2][j-2]+1); } if(i>=2&&j>=3&&s1[i-2]==s2[j-1]&&s1[i-1]==s2[j-3]){ d[i][j]=min(d[i][j],d[i-2][j-3]+2); } if(i>=3&&j>=2&&s1[i-1]==s2[j-2]&&s1[i-3]==s2[j-1]){ d[i][j]=min(d[i][j],d[i-3][j-2]+2); } } } return d[l1][l2]; } int main(){ // freopen("test.in","r",stdin); while(scanf("%d",&n),n){ scanf("%d",&dis); for(int i=0;i<n;i++)scanf("%s",st[i].s); sort(st,st+n); int tot=0; for(int i=0;i<n;i++){ for(int j=i+1;j<n;j++){ int x=dp(st[i].s,st[j].s); if(x<=dis){ tot++; printf("%s,%s\n",st[i].s,st[j].s); } } } printf("%d\n",tot); } return 0; }