【字典树 回溯】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 = p1c ,p2c = p2c

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++**实现。

相关推荐
林爷万福4 分钟前
光谱数据预处理:基线校正、平滑去噪实战
人工智能·算法
8Qi818 分钟前
LeetCode 1049:最后一块石头的重量 II —— 题解 ✅
算法·leetcode·职场和发展·动态规划·01背包
wuminyu31 分钟前
Java锁机制之Java对象重量级锁源码剖析
java·linux·c语言·jvm·c++
wubba lubba dub dub75032 分钟前
第四十九周学习周报
人工智能·算法·机器学习
Java_2017_csdn1 小时前
ComplexKeysShardingAlgorithm 小结
java·大数据·算法
海梨花1 小时前
快手面试高频算法题
java·算法·面试
lqqjuly1 小时前
超分辨率算法深度解析(Super-Resolution Algorithms)
算法
郝学胜_神的一滴1 小时前
Qt 高级开发 026:QTabWidget御道,从筑基到化境
c++·qt
apocelipes2 小时前
GNU GCC 多版本函数扩展
c语言·c++·linux编程
代码中介商2 小时前
C++完美转发与引用折叠全解析
开发语言·c++