【字典树 回溯】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++**实现。

相关推荐
chase。13 小时前
【学习笔记】从经典算法到通用神经运动规划器
笔记·学习·算法
王璐WL13 小时前
【C++】经典且易错的题
c++
feasibility.14 小时前
OpenCV图像滤波算法应用:常见滤波器的原理与效果对比(含c++/python代码与中文显示)
c++·opencv·算法
Rabitebla14 小时前
快速排序(QuickSort)完全指南 —— 从原理到工业级优化
c语言·数据结构·c++·算法·github
赫瑞14 小时前
Java中的图论2——Kruskal算法
java·算法·图论
XiYang-DING14 小时前
【LeetCode】206. 反转链表
算法·leetcode·链表
迷途之人不知返14 小时前
string
c++
liulilittle14 小时前
OPENPPP2 CTCP 协议栈 + 内置 TC Hairpin NAT 内核态程序
c语言·开发语言·网络·c++·信息与通信·通信
_深海凉_14 小时前
LeetCode热题100-合并两个有序链表
算法·leetcode·链表
人工智能培训14 小时前
样本效率与安全探索的矛盾解析及平衡路径
大数据·人工智能·深度学习·算法·机器学习·知识图谱·故障诊断