本文涉及知识点
P7210 [COCI 2020/2021 #3] Vlak
题目描述
Nina 和 Emilija 在纸上做游戏。刚开始,纸是空白的。在一个回合里,一个玩家将一个字母加入到纸上一个单词的末尾。接着,二者交替顺序。规定 Nina 为先手。
玩家们选择字母时必须遵循这样的原则:每一次添加字母后的单词必须是该玩家最喜爱歌曲中某个单词的前缀。如果某个玩家无法继续执行它的回合,那么她就输了。
如果两位玩家选择的策略都是最佳的,请判断谁是赢家。
输入格式
第一行包含一个正整数 n n n,表示 Nina 最喜爱歌曲中的单词数量。
接下来的 n n n 行,每一行输入 Nina 最喜爱歌曲中的一个单词。
接下来的一行包含一个正整数 m m m,表示 Emilija 最喜爱歌曲中的单词数量。
接下来的 m m m 行,每一行输入 Emilija 最喜爱歌曲中的一个单词。
输入的单词都只包含小写字母,且单词长度总和不超过 200000 200000 200000。
输出格式
输出获胜的玩家,Nina 或 Emilija。
输入输出样例 #1
输入 #1
2
aaa
bbb
3
aab
aba
bbb
输出 #1
Nina
输入输出样例 #2
输入 #2
2
acg
beh
2
adi
bfj
输出 #2
Emilija
输入输出样例 #3
输入 #3
3
ja
sam
vlak
5
sto
zgazit
ce
te
mali
输出 #3
Nina
说明/提示
样例 1 解释
如果 Nina 先写下字母 b,那么 Emilija 将必须写下 b,然后 Nina 将继续写下 b。则当前的单词为 bbb,而 Emilija 将无法继续执行下一步,因此 Nina 获胜。
如果 Nina 先写下字母 a,那么 Emilija 将写下 b。单词将变成 ab,因此 Nina 将无法继续执行下一步,因此她将输掉。
数据规模与约定
对于其中 40 40 40 分的数据,单词长度总和不超过 2000 2000 2000。
对于 100 % 100\% 100% 的数据,单词长度总和不超过 200000 200000 200000。
说明
本题分值按 COCI 原题设置,满分 70 70 70。
题目译自 COCI2020-2021 CONTEST #3 T2 Vlak。
字典树 回溯
字典树tree1记录Nina喜欢的歌词,tree2记录 Emilija喜欢的歌词。如果某个单词是前缀,那一定是树上的节点。
回溯函数bool Track(p1,p2),当前玩家胜利,返回true;否则,返回false。
字典树的节点p1指向执行当前玩家的节点,p2指向两外一个玩家。
如果p1没有子节点return false;
如果p1存在某个节点,p2没有,return ture。
依次递归回溯所有p1和p2共同存在的字符c,p1c = p1[c] ,p2c = p2[c]。
Track(p2c,p1c)
初始调用:Track(tree1的根节点,tree2的根节点)
时间复杂度:O(|S|)。 任意合法状态都是两位玩家的前缀。
代码
核心代码
cpp
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
vector<T> ret;
T tmp;
while (cin >> tmp) {
ret.emplace_back(tmp);
if ('\n' == cin.get()) { break; }
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class TData = char, int iTypeNum = 26, TData cBegin = 'a'>
class CTrieNode
{
public:
~CTrieNode()
{
for (auto& [tmp, ptr] : m_dataToChilds) {
delete ptr;
}
}
CTrieNode* AddChar(TData ele, int& iMaxID)
{
#ifdef _DEBUG
if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
{
return nullptr;
}
#endif
const int index = ele - cBegin;
auto ptr = m_dataToChilds[ele - cBegin];
if (!ptr)
{
m_dataToChilds[index] = new CTrieNode();
#ifdef _DEBUG
m_dataToChilds[index]->m_iID = ++iMaxID;
m_childForDebug[ele] = m_dataToChilds[index];
#endif
}
return m_dataToChilds[index];
}
CTrieNode* GetChild(TData ele)
{
#ifdef _DEBUG
if ((ele < cBegin) || (ele >= cBegin + iTypeNum))
{
return nullptr;
}
#endif
return m_dataToChilds[ele - cBegin];
}
const map<int, CTrieNode*>& Datas()const { return m_dataToChilds; }
protected:
#ifdef _DEBUG
int m_iID = -1;
std::unordered_map<TData, CTrieNode*> m_childForDebug;
#endif
public:
int m_iLeafIndex = -1;
protected:
//CTrieNode* m_dataToChilds[iTypeNum] = { nullptr };//空间换时间 大约216字节
//unordered_map<int, CTrieNode*> m_dataToChilds;//时间换空间 大约56字节
map<int, CTrieNode*> m_dataToChilds;//时间换空间,空间略优于哈希映射,数量小于256时,时间也优。大约48字节
};
template<class TData = char, int iTypeNum = 26, TData cBegin = 'a'>
class CTrie
{
public:
int GetLeadCount()
{
return m_iLeafCount;
}
CTrieNode<TData, iTypeNum, cBegin>* AddA(CTrieNode<TData, iTypeNum, cBegin>* par, TData curValue)
{
auto curNode = par->AddChar(curValue, m_iMaxID);
FreshLeafIndex(curNode);
return curNode;
}
template<class IT>
int Add(IT begin, IT end)
{
auto pNode = &m_root;
for (; begin != end; ++begin)
{
pNode = pNode->AddChar(*begin, m_iMaxID);
}
FreshLeafIndex(pNode);
return pNode->m_iLeafIndex;
}
template<class IT>
CTrieNode<TData, iTypeNum, cBegin>* Search(IT begin, IT end)
{
auto ptr = &m_root;
for (; begin != end; ++begin)
{
ptr = ptr->GetChild(*begin);
if (nullptr == ptr)
{
return nullptr;
}
}
return ptr;
}
CTrieNode<TData, iTypeNum, cBegin> m_root;
protected:
void FreshLeafIndex(CTrieNode<TData, iTypeNum, cBegin>* pNode)
{
if (-1 == pNode->m_iLeafIndex)
{
pNode->m_iLeafIndex = m_iLeafCount++;
}
}
int m_iMaxID = 0;
int m_iLeafCount = 0;
};
class Solution {
public:
bool Ans(vector<string>& strs1, vector<string>& strs2) {
CTrie<> trie1, trie2;
for (const auto& s : strs1) {
trie1.Add(s.begin(), s.end());
}
for (const auto& s : strs2) {
trie2.Add(s.begin(), s.end());
}
function<bool(const CTrieNode<>*, const CTrieNode<>*)> BackTrack = [&](const CTrieNode<>* p1, const CTrieNode<>* p2) {
if (nullptr == p1) {
return false;
}
const auto& data1 = p1->Datas();
if (data1.empty()) {
return false;
}
if (nullptr == p2) { return true; }
const auto& data2 = p2->Datas();
if (data2.empty()) {
return true;
}
for (const auto& [tmp, p11] : data1) {
if (!data2.count(tmp)) {
return true;
}
}
bool bAllFail = true;
for (const auto& [tmp, p11] : data1) {
auto it = data2.find(tmp);
bAllFail &= BackTrack(it->second, p11);
}
return !bAllFail;
};
return BackTrack(&trie1.m_root, &trie2.m_root);
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0); cin.tie(nullptr);
auto strs1 = Read<string>();
auto strs2 = Read<string>();
#ifdef _DEBUG
//printf("N=%d",n);
Out(strs1, "strs1=");
Out(strs2, ",strs2=");
//Out(que, ",que=");
/*Out(que, "que=");*/
#endif // DEBUG
auto res = Solution().Ans(strs1,strs2);
cout << (res?"Nina":"Emilija") << "\n";
return 0;
}
单元测试
cpp
vector<string> strs1,strs2;
TEST_METHOD(TestMethod01)
{
strs1 = { "a","b" }, strs2 = { "ab" };
auto res = Solution().Ans(strs1, strs2);
AssertEx(true, res);
}
TEST_METHOD(TestMethod02)
{
strs1 = { "a" }, strs2 = { "ab" };
auto res = Solution().Ans(strs1, strs2);
AssertEx(false, res);
}
TEST_METHOD(TestMethod11)
{
strs1 = { "aaa","bbb" }, strs2 = { "aab","aba","bbb" };
auto res = Solution().Ans(strs1,strs2);
AssertEx(true, res);
}
TEST_METHOD(TestMethod12)
{
strs1 = { "acg","beh" }, strs2 = { "adi","bfj" };
auto res = Solution().Ans(strs1, strs2);
AssertEx(false, res);
}
TEST_METHOD(TestMethod13)
{
strs1 = { "ja","sam","vlak" }, strs2 = { "sto","zgazit","ce","te","mali" };
auto res = Solution().Ans(strs1, strs2);
AssertEx(true, res);
}
扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。