【字典树 回溯】P7210 [COCI 2020/2021 #3] Vlak|普及+

本文涉及知识点

C++回溯
C++前缀树(字典树)

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。

输出格式

输出获胜的玩家,NinaEmilija

输入输出样例 #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++**实现。

相关推荐
大江东去浪淘尽千古风流人物2 小时前
【claw】 OpenClaw 的架构设计探索
深度学习·算法·3d·机器人·slam
Vect__2 小时前
深刻理解C++STL库常见容器功能和底层
开发语言·c++
夏玉林的学习之路2 小时前
委托构造和using关键字
开发语言·c++·算法
small-pudding2 小时前
深入理解PDF:蒙特卡洛光线追踪中的概率密度函数
算法·pdf·图形渲染
历程里程碑2 小时前
Linux 46 HTTPS(协议原理)安全通信全流程解析
linux·网络·c++·网络协议·http·https·排序算法
We་ct2 小时前
LeetCode 46. 全排列:深度解析+代码拆解
前端·数据结构·算法·leetcode·typescript·深度优先·回溯
逆境不可逃2 小时前
LeetCode 热题 100 之 763.划分字母区间
算法·leetcode·职场和发展
MicroTech20252 小时前
微算法科技(NASDAQ:MLGO)量子PBFT改进技术:重构联盟链共识的效率与安全
科技·算法·重构
程序员小明儿2 小时前
量子计算探秘:从零开始的量子编程与算法之旅 · 第二篇
算法·量子计算