P8371 绿色游戏 题解
题目传送门
题解:
题目大意:
Ann 和 Billy 在一个棋盘上移动
思路分析:
我们设
- 当
i \le a 时:(b _ {w} 表示i 的第w 个后继点)f _ {i} = f _ {b _ {1}} \operatorname{or} f _ {b _ {2}} \operatorname{or} \cdots \operatorname{or} f _ {b _ {n}} ,因为当前由 Ann 操控,他可以选择去一个有必胜策略的点。 - 当
i > a 时:(b _ {w} 表示i 的第w 个后继点)f _ {i} = f _ {b _ {1}} \operatorname{and} f _ {b _ {2}} \operatorname{and} \cdots \operatorname{and} f _ {b _ {n}} ,因为当前由 Billy 操控,只要有不让 Ann 胜利的点,他就会去,所以 Ann 能胜利当且仅当这个点所有后继点都能让 Ann 胜利。
到这里我们遇到了一个问题,这个题的图存在环,所以我们无法常规计算。经过我在英语课上的不断思索,发现可以迭代求解。
所谓迭代,就是反复通过旧数据计算新数据,并将结果用到下一次计算中,从而不断逼近直至找到正确答案。本题我们可以搞一个集合 Q ,然后具体操作如下:
-
把目前的绿点全加入集合。
-
从绿点向外扩展到每个点,对于第
i 个点来说,如果i \le a ,那么他只要有后继点是绿点就把它加进来并标记为绿点,如果i > a ,那么只要他所有的后继点都是绿点就把它加入集合并标记为绿点。 -
通过以上操作,有一些绿点其实是不符合递推式的,所以对于绿点
i ,如果i \le a ,那么只要他所有后继点都不在Q 中,就把它搞出去并标记为非绿点,如果i > a ,那么只要他有一个点不在Q 中,就把它踢出集合并标记为非绿点。
最重要的一点,我们要重复这个过程,直到迭代变得稳定。(如果不进行这一步,就会只得 27 分)。
具体来说,我们要让所有绿点都符合条件才能停止,否则答案是错误的。有两种方法:一是强制执行
(本人存图喜欢用 vector,本题也可以用其他方式,集合 Q 用队列 queue 维护即可)。
100pts Code:
#include "iostream"
#include "cstdio"
#include "queue"
#include "vector"
using namespace std;
int a,b,col[6005],bac[6005],f[6005],ans;//col[i] 表示第 i 个点的颜色;bac[i] 表示第 i 个点的后继点数目
vector<int> e[6005];//存图
bool work()
{
queue <int> q;
int tmp[6005];//因为后期 bac 数组要修改,所以先存到另外一个数组
for (int i = 1;i <= b;i++)
{
tmp[i] = bac[i];
f[i] = col[i];
if (f[i] == 1)q.push(i);//把所有绿点加入
}
while (!q.empty())//类似bfs
{
int cur = q.front();
q.pop();
for (auto i:e[cur])
{
if (f[i] == 0)
{
tmp[i]--;//每有一个后继点成为绿点就减一,为零时就合法,入集合
if (tmp[i] == 0 || i <= a)
{
f[i] = 1;
q.push(i);
}
}
}
}
for (int i = 1;i <= b;i++)
{
tmp[i] = bac[i];
if (f[i] == 0)//找非绿点,加入集合,这次要往外筛
{
q.push(i);
}
}
while (!q.empty())
{
int cur = q.front();
q.pop();
for (auto i:e[cur])
{
if (f[i] == 1)
{
tmp[i]--;//每有一个后继点成为非绿点就减一,为0时不合法,出集合
if (tmp[i] == 0||i > a)
{
f[i] = 0;
q.push(i);
}
}
}
}
bool fl = 0;
for (int i = 1;i <= b;i++)
{
if (col[i] == 1 && f[i] == 0)//统计是否有绿点被修改(迭代是否稳定),并修改颜色,作为下一次计算的初始数据
{
col[i] = 0;
fl = 1;
}
}
return fl;
}
int main()
{
scanf("%d%d",&a,&b);
b += a; //此时 a+b 就是点的总数
for (int i = 1;i <= b;i++)
{
scanf("%d%d",&col[i],&bac[i]);
for (int j = 1;j <= bac[i];j++)
{
int u;
scanf("%d",&u);
e[u].push_back(i);//反向建边,便于计算 f[i]
}
}
while (work());//work函数是bool类型的,只要返回值为一就继续进行,这里也可以重复执行点的总数量次。
for (int i = 1;i <= b;i++)
{
if (f[i] == 1)
{
ans++;
}
}
printf("%d\n",ans);//先输出个数
for (int i = 1;i <= b;i++)
{
if (f[i] == 1)
{
printf("%d\n",i);
}
}
return 0;
}