当前位置: 首页 > news >正文

【博弈 拓扑序 缩点】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 1k1,使得 ∀i∈[1,k)\forall i \in [1,k)i[1,k),存在有向边 ai→ai+1a_i \to a_{i+1}aiai+1,且 a1=ua_1=ua1=uak=va_k=vak=v

输入格式

本题有多组测试数据。

第一行一个正整数 TTT,代表数据组数。

对于每组测试数据:

第一行两个整数 n,mn, mn,m,代表图的点数,边数。

第二行 nnn 个整数,代表每个点开始时的颜色。111 代表黑色,000 代表白色。

接下来 mmm 行,每行两个整数 u,vu, vu,v 代表一条从 u→vu \to vuv 的边。

输出格式

对于每组测试数据:

如果最后 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 2n2m≤1m \leq 1m1T=1T=1T=1
  • Subtask 2(15 points):n≤5n \leq 5n5m≤8m \leq 8m8T=1T=1T=1
  • Subtask 3(25 points):保证所有点的初始颜色相同。
  • Subtask 4(55 points):无特殊限制。

对于所有测试数据,1≤n≤1051 \leq n \leq 10^51n1051≤m≤2×1051 \leq m \leq 2 \times 10^51m2×1051≤T≤151 \le T \le 151T15

样例解释

在第一组数据中,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++**实现。

http://www.dtcms.com/a/319188.html

相关文章:

  • Bosco-and-Mancuso Filter for CFA Image Denoising
  • 如何快速掌握Excel公式?14天轻松通关
  • LeetCode 面试经典 150_数组/字符串_除自身以外数组的乘积(13_238_C++_中等)(前缀积)
  • Swift 实战:高效设计 Tic-Tac-Toe 游戏逻辑(LeetCode 348)
  • 解决chrome下载crx文件被自动删除,加载未打包的扩展程序时提示“无法安装扩展程序,因为它使用了不受支持的清单版本解决方案”
  • 冷库温湿度物联网监控系统解决方案:冷链智能化
  • 普通冷库如何升级物联网冷库?工业智能网关赋能冷链智能化转型
  • AI摄像机如何为煤矿减少90%违规事故?
  • HarmonyOS 页面跳转新方案:HMRouter 路由框架全方位使用指南与实践案例
  • Axure 高阶设计:打造“以假乱真”的多图片上传组件
  • 如何使用vLLM运行gpt-oss
  • Nodejs》》MySql
  • 单链表专题---暴力算法美学(1)(有视频演示)
  • Keil MDK-ARM V5.42a 完整安装教程
  • 如何使用Ollama在本地运行gpt-oss
  • 09-netty基础-手写rpc-原理-01
  • 上位机知识篇---aptapt-get
  • 全栈:怎么把sql导入SQLserver里面
  • [特殊字符] 2025年生成式大模型部署与推理优化全景解析
  • STM32 串口控制电机运行系统
  • PyTorch + PaddlePaddle 语音识别
  • 【基础】go进阶学习笔记
  • Android渲染/合成底层原理详解
  • B 站 SEO 优化全景指南:从基础到进阶的实操方法
  • 贪心+矩阵算法
  • Oracle 关闭 impdp任务
  • 云原生安全挑战与治理策略:从架构思维到落地实践
  • 基于大数据的美食视频播放数据可视化系统 Python+Django+Vue.js
  • 解读 gpt-oss-120b 和 gpt-oss-20b开源模型
  • 仓库管理系统-20-前端之记录管理的联表查询