【博弈 拓扑序 缩点】P9220 「TAOI-1」椎名真昼|省选-
本文涉及知识点
C++图论 缩点 拓扑序
博弈
P9220 「TAOI-1」椎名真昼
题目背景
请注意赛后题目添加了多测。因此请将您的赛时代码进行修改后再提交。
题目描述
你正在看轻小说,突然你的家长走了进来,于是你假装在写 OI 题目。
Alice 和 Bob 正在玩一款游戏,给定一个有向图,每个点初始有一个颜色(黑或白)。
双方轮流进行操作,Alice 先手,每次操作选定一个节点,将所有从该点开始,能到达的点(包括自身)颜色翻转。如果某次操作后所有节点都变为白色,则进行该次操作的人胜利。
假如双方都采用最优策略使得自己胜利,或者如果自己无法胜利,使得对方无法胜利。
给你节点的初始状态,请你求出最终的胜者,亦或者,没有胜者。
定义点 uuu 能到达点 vvv,当且仅当存在数列 (a1,a2,a3,⋯,ak)(a_1,a_2,a_3,\cdots,a_k)(a1,a2,a3,⋯,ak),其中 k≥1k \ge 1k≥1,使得 ∀i∈[1,k)\forall i \in [1,k)∀i∈[1,k),存在有向边 ai→ai+1a_i \to a_{i+1}ai→ai+1,且 a1=ua_1=ua1=u,ak=va_k=vak=v。
输入格式
本题有多组测试数据。
第一行一个正整数 TTT,代表数据组数。
对于每组测试数据:
第一行两个整数 n,mn, mn,m,代表图的点数,边数。
第二行 nnn 个整数,代表每个点开始时的颜色。111 代表黑色,000 代表白色。
接下来 mmm 行,每行两个整数 u,vu, vu,v 代表一条从 u→vu \to vu→v 的边。
输出格式
对于每组测试数据:
如果最后 Alice 胜利,输出 A
。
如果最后 Bob 胜利,输出 B
。
如果双方(在对方的阻止下)都无法胜利,输出 N
。
您无需输出空格或换行符。
输入输出样例 #1
输入 #1
2
2 1
1 0
2 1
3 2
1 0 1
1 2
2 3
输出 #1
AN
说明/提示
数据范围
本题采用捆绑测试。
- Subtask 1(5 points):n≤2n \leq 2n≤2,m≤1m \leq 1m≤1,T=1T=1T=1。
- Subtask 2(15 points):n≤5n \leq 5n≤5,m≤8m \leq 8m≤8,T=1T=1T=1。
- Subtask 3(25 points):保证所有点的初始颜色相同。
- Subtask 4(55 points):无特殊限制。
对于所有测试数据,1≤n≤1051 \leq n \leq 10^51≤n≤105,1≤m≤2×1051 \leq m \leq 2 \times 10^51≤m≤2×105,1≤T≤151 \le T \le 151≤T≤15。
样例解释
在第一组数据中,Alice 可以先手对节点 111 进行操作。操作后所有节点变为白色。
在第二组数据中,双方都没有必胜的方法,因此双方会互相拖延对方阻止对方获胜。
「据说如果无论如何都输出 N
的话,有 45%45\%45% 的几率能够得到正确答案?」
「怎么可能,不会真的有人造出这么蠢的数据吧……」
错误解法:博弈 拓扑序 缩点
性质一:如果存在环,且颜色不同,则环上的颜色永远不会相同。故环上颜色相同缩点,不同直接返回平局。
性质二:两个回合选择同一个棋子,则相互抵消。故第K回合不胜利,则K+2回合一定不会胜利。K+1回合选择第K回合的棋子。如果K+2回合能胜利。则第K回合选择相同棋子也能胜利。
结论一:如果第0回合和第1回合不能胜利,则平局。
缩点、去重、去掉自环。
性质三: u子树包括所有黑色棋子,不包括任意白色棋子。选择u,A胜利。
性质四 :全部是白棋,B胜利。B选择和A相同的棋子。
性质五; 入度为0的节点为根节点,可能有多个根。排除全白,存在白色的根,必定平局。A选择白色的根,B必须抵消A的操作。
性质六: 有两个黑色的根,且包括其它棋子x。则平局,A选择x。只有两个黑色的根,B胜。
性质七:有一个黑色的根,一个白色的叶子。B胜利。A选择叶子,2个棋子都是黑色,A选择根。A选择根,根白叶子黑,B选择叶子。一个黑色的根,任何黑色的非根,平局;A将分根边白。一个黑色的根,2个或更多的白色,将以个非根变黑。
实现
缩点、去重、去掉自环。
cntb 记录白色数量,全白返回B。
两颗棋子,全黑,且没有边,返回B。
两颗棋子,黑指向白。返回B。
拓扑序计算cnt[u]:
cnt[u] = u子树中黑色棋子数量 - u子树中白色棋子数量。
任意cnt[u] == N1 - cntb
正确解法
错误原因:节点4可能被计算两次。
如果存在黑棋,令TopSort最大的为u(排除被缩点的点)
vis[u]=1
拓扑序逆序枚举cur
if(!vis[cur])continue;
for(auto& v : nei[u]){ vis[v]=1;};
确保每条边只执行一次。
修改被缩的点的vis值。
最后判断vis和ws是否相等。
代码
核心代码
#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 T1, class T2, class T3, class T4,class T5 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4,T5>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t);return in;
}template<class T1, class T2, class T3, class T4, class T5,class T6 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4, T5,T6>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t) >>get<5>(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<int N = 1'000'000>
class COutBuff
{
public:COutBuff() {m_p = puffer;}template<class T>void write(T x) {int num[28], sp = 0;if (x < 0)*m_p++ = '-', x = -x;if (!x)*m_p++ = 48;while (x)num[++sp] = x % 10, x /= 10;while (sp)*m_p++ = num[sp--] + 48;AuotToFile();}void writestr(const char* sz) {strcpy(m_p, sz);m_p += strlen(sz);AuotToFile();}inline void write(char ch){*m_p++ = ch;AuotToFile();}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);m_p = puffer;}~COutBuff() {ToFile();}
private:inline void AuotToFile() {if (m_p - puffer > N - 100) {ToFile();}}char puffer[N], * m_p;
};template<int N = 1'000'000>
class CInBuff
{
public:inline CInBuff() {}inline CInBuff<N>& operator>>(char& ch) {FileToBuf();ch = *S++;return *this;}inline CInBuff<N>& operator>>(int& val) {FileToBuf();int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行 return *this;}inline CInBuff& operator>>(long long& val) {FileToBuf();long long x(0); int f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行return *this;}template<class T1, class T2>inline CInBuff& operator>>(pair<T1, T2>& val) {*this >> val.first >> val.second;return *this;}template<class T1, class T2, class T3>inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val);return *this;}template<class T1, class T2, class T3, class T4>inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);return *this;}template<class T = int>inline CInBuff& operator>>(vector<T>& val) {int n;*this >> n;val.resize(n);for (int i = 0; i < n; i++) {*this >> val[i];}return *this;}template<class T = int>vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {*this >> ret[i];}return ret;}template<class T = int>vector<T> Read() {vector<T> ret;*this >> ret;return ret;}
private:inline void FileToBuf() {const int canRead = m_iWritePos - (S - buffer);if (canRead >= 100) { return; }if (m_bFinish) { return; }for (int i = 0; i < canRead; i++){buffer[i] = S[i];//memcpy出错 }m_iWritePos = canRead;buffer[m_iWritePos] = 0;S = buffer;int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);if (readCnt <= 0) { m_bFinish = true; return; }m_iWritePos += readCnt;buffer[m_iWritePos] = 0;S = buffer;}int m_iWritePos = 0; bool m_bFinish = false;char buffer[N + 10], * S = buffer;
};class CNeiBo
{
public:static vector<vector<int>> Two(int n, const vector<pair<int, int>>& edges, bool bDirect, int iBase = 0){vector<vector<int>> vNeiBo(n);for (const auto& [i1, i2] : edges){vNeiBo[i1 - iBase].emplace_back(i2 - iBase);if (!bDirect){vNeiBo[i2 - iBase].emplace_back(i1 - iBase);}}return vNeiBo;}static vector<vector<int>> Two(int n, const vector<vector<int>>& edges, bool bDirect, int iBase = 0){vector<vector<int>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);}}return vNeiBo;}static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0){vector<vector<std::pair<int, int>>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);}}return vNeiBo;}static vector<vector<std::pair<int, int>>> Three(int n, const vector<tuple<int, int, int>>& edges, bool bDirect, int iBase = 0){vector<vector<std::pair<int, int>>> vNeiBo(n);for (const auto& [u, v, w] : edges){vNeiBo[u - iBase].emplace_back(v - iBase, w);if (!bDirect){vNeiBo[v - iBase].emplace_back(u - iBase, w);}}return vNeiBo;}static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat){vector<vector<int>> neiBo(neiBoMat.size());for (int i = 0; i < neiBoMat.size(); i++){for (int j = i + 1; j < neiBoMat.size(); j++){if (neiBoMat[i][j]){neiBo[i].emplace_back(j);neiBo[j].emplace_back(i);}}}return neiBo;}
};class CDGTopSort
{
public:template <class T = vector<int> >CDGTopSort(const vector<T>& vNeiBo) :m_vDeg(vNeiBo.size()), m_neiBo(vNeiBo) {const int N = vNeiBo.size();m_backNeiBo.resize(N);for (int cur = 0; cur < N; cur++){m_vDeg[cur] = vNeiBo[cur].size();for (const auto& next : vNeiBo[cur]){m_backNeiBo[next].emplace_back(cur);}}}void Init() {auto Add = [&](int i) {if (0 != m_vDeg[i]) { return; }m_que.emplace(i);};for (int i = 0; i < m_vDeg.size(); i++){Add(i);}while (m_que.size()){const int cur = m_que.front(); m_que.pop();if (!OnDo(cur)) { continue; }for (const auto& next : m_backNeiBo[cur]){m_vDeg[next]--;Add(next);}};}queue<int> m_que;vector<int> m_vDeg;vector<int> m_vSort;
protected:const vector<vector<int>>& m_neiBo;vector<vector<int>> m_backNeiBo;virtual bool OnDo(int cur) {m_vSort.emplace_back(cur);return true;};
};class CSCCTarjan {
public:CSCCTarjan(vector<vector<int>>& neiBo) :m_neiBo(neiBo) {const int N = neiBo.size();m_vTime.assign(N, -1);m_vBack.assign(N, -1);m_vIsStack.assign(N, false);for (int i = 0; i < N; i++) {DFS(i);}}void InitPtNew() {m_ptNew.resize(m_neiBo.size());iota(m_ptNew.begin(), m_ptNew.end(), 0);for (auto& v : m_sccs) {nth_element(v.begin(), v.begin(), v.end());m_v0.emplace_back(v[0]);for (int i = 1; i < v.size(); i++) {m_ptNew[v[i]] = v[0];}}}vector<int> RingSize() {vector<int> ringSize(m_ptNew.size());for (const auto& i : m_ptNew) {ringSize[i]++;}return ringSize;}vector<vector<int>> GetNewNeiBo() {vector<vector<int>> neiBo(m_neiBo.size());for (int i = 0; i < neiBo.size(); i++) {const int n1 = m_ptNew[i];for (const auto& next : m_neiBo[i]) {const int n2 = m_ptNew[next];if (n1 == n2) { continue; }//自环neiBo[n1].emplace_back(n2);}}for (int i = 0; i < neiBo.size(); i++) {unordered_set<int> s(neiBo[i].begin(), neiBo[i].end());vector<int> tmp(s.begin(), s.end());neiBo[i].swap(tmp);}return neiBo;}vector<vector<int>> m_sccs;vector<int> m_v0, m_ptNew;
protected:void DFS(int cur) {if (-1 != m_vTime[cur]) { return; }m_vTime[cur] = m_vBack[cur] = m_iTimes++;m_vIsStack[cur] = true;m_sta.emplace(cur);for (const auto& next : m_neiBo[cur]) {if (-1 == m_vTime[next]) {DFS(next);m_vBack[cur] = min(m_vBack[cur], m_vBack[next]);}else if (m_vIsStack[next]) {m_vBack[cur] = min(m_vBack[cur], m_vTime[next]);}}if (m_vTime[cur] != m_vBack[cur]) { return; }vector<int> scc;while (m_sta.size()){auto u = m_sta.top(); m_sta.pop();scc.emplace_back(u);m_vIsStack[u] = false;if (cur == u) { break; }}m_sccs.emplace_back(scc);}vector<vector<int>>& m_neiBo;int m_iTimes = 0;vector<int> m_vTime, m_vBack;vector<bool> m_vIsStack;stack<int> m_sta;
};class Solution {public:string Ans(vector<int>& ws, vector<pair<int, int>>& edge) {const int N1 = ws.size();auto neiBo = CNeiBo::Two(N1, edge, true, 1);CSCCTarjan scc(neiBo);scc.InitPtNew();for (auto& v : scc.m_sccs) {for (int i = 1; i < v.size(); i++) {if (ws[v[0]] != ws[v[i]]) { return "N"; }}}auto nei = scc.GetNewNeiBo();if (2 == scc.m_v0.size()) {const int i1 = scc.m_v0.front(), i2 = scc.m_v0.back();const int tmp = ws[i1] + ws[i2];if ((2 == tmp) && (nei[i1].size() + nei[i2].size() == 0)) { return "B"; }if (1 == tmp){if (2 == ws[i1] + nei[i1].size()) { return "B"; }if (2 == ws[i2] + nei[i2].size()) { return "B"; }}}CDGTopSort topSort(nei);topSort.Init();int root = -1;for (const auto& u : topSort.m_vSort) {if ((u != scc.m_ptNew[u])||(0==ws[u])){ continue; }root = u;}if(-1 == root) { return "B"; }//全白vector<int> vis(N1);vis[root] = true;for (const auto& u : vector<int>(topSort.m_vSort.rbegin(), topSort.m_vSort.rend())) {if (!vis[u]) { continue; }for (const auto& v : nei[u]) {vis[v] = true;} }for (int i = 0; i < N1; i++) {vis[i] = vis[scc.m_ptNew[i]];}if (vis == ws) { return "A"; }return "N";}};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUGios::sync_with_stdio(0); cin.tie(nullptr); cout.tie(nullptr); int T,N, M;cin >> T;for (int i = 0; i < T; i++){cin >> N >> M;auto ws = Read<int>(N);auto edge = Read<pair<int, int>>(M);
#ifdef _DEBUG//printf("N=%d,S=%d,P=%d", N, S, P);Out(ws, "ws=");Out(edge, ",edge=");//Out(fun, ",fun=");//Out(que, ",que=");
#endif // DEBUGauto res = Solution().Ans(ws, edge);cout << res;}return 0;
}
单元测试
vector<int> ws;vector<pair<int, int>> edge;TEST_METHOD(TestMethod01){ws = { 1 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod02){ws = { 0 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod03){ws = { 1,1 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod04){ws = { 1,1 }, edge = { {1,2} };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod05){ws = { 1,1 }, edge = { {2,1} };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod06){ws = { 0,0 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod07){ws = { 0,0 }, edge = { {1,2} };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod08){ws = { 0,0 }, edge = { {2,1} };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod10){ws = { 0,1 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod11){ws = { 0,1 }, edge = { {1,2} };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod12){ws = { 0,1 }, edge = { {2,1} };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod13){ws = { 1,0 }, edge = { };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod14){ws = { 1,0 }, edge = { {1,2} };auto res = Solution().Ans(ws, edge);AssertEx("B", res.c_str());}TEST_METHOD(TestMethod15){ws = { 1,0 }, edge = { {2,1} };auto res = Solution().Ans(ws, edge);AssertEx("A", res.c_str());}TEST_METHOD(TestMethod16){ws = { 1,0,1 }, edge = { {1,2},{2,3} };auto res = Solution().Ans(ws, edge);AssertEx("N", res.c_str());}TEST_METHOD(TestMethod17){ws = { 1,0 }, edge = { {1,2},{2,1} };auto res = Solution().Ans(ws, edge);AssertEx("N", res.c_str());}TEST_METHOD(TestMethod18){ws = { 1,1,1,1,1 }, edge = { {1,2},{1,3} ,{2,4},{3,4} };auto res = Solution().Ans(ws, edge);AssertEx("N", res.c_str());}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步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++**实现。