PC/UVa:110905/10029
如果一个单词可以通过增减、删除或者替换一个字母变成另一个单词,则两个单词构成递变(书中的翻译,英文原文是Edit Step)关系。给定一个字典(排好序),求字典中最长的字典序增加的递变序列。
第一步要找出哪些单词之间存在字典序的递变关系(构建图),第二步在图中找最长路径。
因为做这道题之前看了《数据结构与算法分析——C++语言描述(第四版)》,书中4.8.4节介绍了类似的算法:
- 第一种是对给定的单词两两比较,
O(n ^ 2)
的算法,时间比较慢; - 第二种改进的是对长度进行分组,还是
O(n ^ 2)
的算法,但是时间提高很多
受刘汝佳的书的启发,将字符串转换为整数ID,这样可以减少字符串的比较查找操作。
算法上,这题用的第三种改进方式,在按照长度分组的基础上,对每一组中的每个单词,依次去掉每一个字母,将剩余部分作为键,原单词作为值,构造这样的一个键值对map<string, vector<size_t>
。这样如果两个单词去掉一个字母后,剩余的部分相同,则它们就可以通过一次递变得到,例如fine
和wine
,在去掉f
和w
后,剩下的是ine
。要注意的是,一个单词去掉不同位置的字母后剩下的部分是一样的,所以vector
中会有重复的。
书中介绍的算法到此就结束了,但是其实这有一个小bug,那就是不同单词去掉不同位置的一个字母后,剩下的部分也可能一样,例如at
和to
。
上述处理完毕后很容易得到长度相同的单词之间的递变关系,下面还需要长度差1
之间的递变关系(题目中的增加和删除)。这可以借助上面构建的键值对。对于一个单词,查找是否有上面的键和它相同,如果有,那么就存在这种递变关系,注意添加这种单向边要满足字典序(不过不满足字典序也没关系,因为后边的第二步最长路径也有限制,但是加上限制而不是一味地插入运行会快一些,毕竟是set
操作),这可以通过uDebug上的第一个例子测出来。
第二步是找最长路径,通过看别人的博客,发现可以变成最长上升子序列的问题。如果一前一后两个单词,存在上述单向边,则长度加1
。
另外,uDebug上Morass贡献的测试用例跑了很长世间,而且结果也不对,但是交到UVa上后AC了。小云儿提示测试用例可能有问题,仔细观察后发现,该测试用例中包含重复的单词,如果去掉重复的单词后再用uDebug跑,结果就一样了。
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <set>
using namespace std;
bool OneCharDiff(const string &str1, const string &str2)
{
size_t cnt = 0;
for (size_t i = 0; i < str1.size(); i++)
{
cnt += str1[i] == str2[i] ? 0 : 1;
if (cnt > 1) return false;
}
return true;
}
void DSAA(const vector<string> &vecDic, const map<string, size_t> &mDic,
vector<set<size_t>> &Graph)
{
map<size_t, vector<size_t>> mLen2Word;
vector<map<string, vector<size_t>>> vecmDel2Word;
//先根据长度对单词分组
for (auto word : vecDic)
{
mLen2Word[word.size()].push_back(mDic.find(word)->second);
}
string strTmp;
vecmDel2Word.reserve(mLen2Word.size());
for (auto iter = mLen2Word.begin(); iter != mLen2Word.end(); iter++)
{
vecmDel2Word.push_back(map<string, vector<size_t>>());
map<string, vector<size_t>> &mDel2Word = vecmDel2Word.back();
const vector<size_t> &wordID = iter->second;
for (size_t ID : wordID)
{
for (size_t pos = 0; pos < iter->first; pos++)
{
strTmp = vecDic[ID];
strTmp.erase(pos, 1);
mDel2Word[strTmp].push_back(ID);
}
}
}
/*
for (size_t idx = 0; idx < vecmDel2Word.size(); idx++)
{
const map<string, vector<size_t>> &mDel2Word = vecmDel2Word[idx];
cout << "Origin length " << mDel2Word.begin()->first.size() + 1 << endl;
for (auto iter = mDel2Word.begin(); iter != mDel2Word.end(); iter++)
{
cout << iter->first << ':';
const vector<size_t> &wordID = iter->second;
for (size_t ID : wordID)
{
cout << vecDic[ID] << ' ';
}
cout << endl;
}
cout << endl;
}
*/
//构建单向边
for (size_t idx = 0; idx < vecmDel2Word.size(); idx++)
{
//在相同的长度的ID之间构建单向边
const map<string, vector<size_t>> &mDel2Word = vecmDel2Word[idx];
for (auto iter = mDel2Word.begin(); iter != mDel2Word.end(); iter++)
{
if (iter->second.size() == 1) continue;
const vector<size_t> &wordID = iter->second;
for (size_t i = 0; i < wordID.size(); i++)
{
for (size_t j = i + 1; j < wordID.size(); j++)
{
if (OneCharDiff(vecDic[wordID[i]], vecDic[wordID[j]])){
//不同的单词删除掉不同位置的字母剩下的内容可能是一样的
//但是这两个不能通过一次递变互相转换
//根据之前的处理可以保证字典序
Graph[wordID[i]].insert(wordID[j]);
}
}
}
}
//在长度差1的ID之间构建单向边
if (idx < vecmDel2Word.size() - 1){
size_t currlen = mDel2Word.begin()->first.size() + 1;
const map<string, vector<size_t>> &mDel2Wordp1 = vecmDel2Word[idx + 1];
if (mDel2Wordp1.begin()->first.size() == currlen){
const vector<size_t> &wordID = mLen2Word.find(currlen)->second;
for (auto ID : wordID)
{
auto iter = mDel2Wordp1.find(vecDic[ID]);
if (iter != mDel2Wordp1.end()){
for (size_t i = 0; i < iter->second.size(); i++)
{
if (vecDic[ID] < vecDic[iter->second[i]]){
Graph[ID].insert(iter->second[i]);
}
if (vecDic[iter->second[i]] < vecDic[ID]){
Graph[iter->second[i]].insert(ID);
}
}
}
}
}
}
}
/*
for (size_t i = 0; i < Graph.size(); i++)
{
cout << vecDic[i] << ':';
for (auto adj : Graph[i])
{
cout << vecDic[adj] << ' ';
}
cout << endl;
}
*/
}
int main()
{
vector<string> vecDic;
map<string, size_t> mDic;
string strWord;
size_t cnt = 0;
while (cin >> strWord){
if (mDic.find(strWord) == mDic.end()){
vecDic.push_back(strWord);
mDic[strWord] = cnt++;
}
}
vector<set<size_t>> Graph(vecDic.size(), set<size_t>());
DSAA(vecDic, mDic, Graph);
vector<size_t> vecLen(Graph.size(), 1);
for (size_t idx = 1; idx < Graph.size(); idx++)
{
size_t max = 1;
for (size_t prev = 0; prev < idx; prev++)
{
auto iter = Graph[prev].find(idx);
if (iter != Graph[prev].end()){
if (max < vecLen[prev] + 1){
max = vecLen[prev] + 1;
}
}
}
vecLen[idx] = max;
}
size_t maxlen = 0;
for (auto len : vecLen)
{
if (len > maxlen) maxlen = len;
}
cout << maxlen << endl;
return 0;
}
/*
cat
dig
dog
fig
fin
fine
fog
log
wine
*/