timus 1002. Phone Numbers(KMP&动态规划)

题目链接

1002. Phone Numbers

题意

现实生活中,你时常会遇到许多许多而且越来越长的电话号码。你需要记住这类型的号码。
例如按下面的图示,把字母划分到特定的数字上,是一种很容易就能把数字记住的方法:

1 ij    2 abc   3 def
4 gh    5 kl    6 mn
7 prs   8 tuv   9 wxy
0 oqz

按这种方法:每个字或一个词组可被代替成一组特定的数字,那么,你只可以通过记住一些词就能记住相应电话号码。
如果可以找出一种单词与个人电话号码的简单关系,它是很有吸引力的。例如你的一个棋友的电话号码是941837296,你可以用
WHITEPAWN来代替;又如你可以用BUULDOG来代替你的一个喜爱的老师的电话号码:2855304。

问题

对给定的给定的数字和单词表,求出一个最简短的单词序列(也就是得出一尽可能短的单词来代替相应的数字)。这种对应关系要求符合上图所描述的关系。

输入

输入包含若干组的测试数据。每组测试点的第一行是你所要记住的电话号码。这个号码最多有100个数位。测试的第二行是单词总数(最大为50000个)。以下的每一
行是只包含一个单词,单词长度最大限制为50个字母。整个输入文件的大小不超过300KB。 输入文件的最后一行以-1作为结束标志。

输出

输出文件的每一行为找到的最短单词序列。每个单词间用一个空格隔开。如果没有解决方案,则输出“No solution.”。
如果有多个单词满足条件,可以从中选择任一个单词输出。

样例

input

7325189087
5
it
your
reality
real
our
4294967296
5
it
your
reality
real
our
-1

output

reality our
No solution.

思路

首先,因为每个字母都对应唯一一个数字,我们完全可以将所有给定单词转换为数字序列。
那么,现在的问题就是用最少的数字串组成主串,我们用kmp找出每个数字串在主串中匹配的位置。
若匹配成功,那么在主串中匹配的首尾位置可以看作是一个权值为1的单向边,问题就可以转换为求0~n的最短路问题。
因为这题的每条边的先后次序是确定的,所以直接用dp解决即可。

代码

#include <iostream>
#include <cstring>
#include <math.h>
#include <cstdio>
#include <stack>

using namespace std;

const int N = 109;
const int M = 50009;
int p[N], d[N], v[N];
int g[N][N];
int fa[N];
int l[M];
char s[59], z[M][59];
int t[26] = {
    2, 2, 2,
    3, 3, 3,
    4, 4,
    1, 1,
    5, 5,
    6, 6,
    0, 7, 0,
    7, 7, 8,
    8, 8, 9,
    9, 9, 0
};

int nxt[59];

void getNext(int d[], int length)
{
    nxt[0] = 0;
    for(int i=1, k=0; i<length; i++)
    {
        while(k && d[i] != d[k])
            k = nxt[k-1];
        nxt[i] = d[i] == d[k] ? ++k : 0;
    }
}

void kmp(int b[], int d[], int len, int length, int pos)
{
    getNext(d, length);
    for(int i=0, k=0; i<len; i++)
    {
        while(k>0 && d[k] != p[i])
            k = nxt[k-1];
        if(d[k] == p[i])
            k++;
        if(k == length)
            g[i-length+1][i+1] = pos;
    }
}

bool solve(int len)
{
    memset(v, 0x3f, sizeof(v));
    v[0] = 0;
    for(int i=0; i<len; i++)
        for(int j=i+1; j<=len; j++)
            if(g[i][j] != -1 && v[i]+1 < v[j])
            {
                v[j] = v[i] + 1;
                fa[j] = g[i][j];
            }
    return v[len] < 0x3f3f3f3f;
}

void print(int now)
{
    if(now - l[fa[now]] > 0)
    {
        print(now-l[fa[now]]);
        printf(" ");
    }
    printf("%s", z[fa[now]]);
}

int main()
{
    int n;
    while(~scanf("%s", s))
    {
        if(s[0] == '-')
            break;
        memset(g, -1, sizeof(g));
        int len = strlen(s);
        for(int i=0; i<len; i++)
            p[i] = s[i]-'0';
        scanf("%d", &n);
        for(int i=0; i<n; i++)
        {
            scanf("%s", z[i]);
            l[i] = strlen(z[i]);
            for(int j=0; j<l[i]; j++)
                d[j] = t[z[i][j]-'a'];
            kmp(p, d, len, l[i], i);
        }
        if(solve(len))
            print(len);
        else
            printf("No solution.");
        printf("\n");
    }
    return 0;
}