题目链接:登录—专业IT笔试面试备考平台_牛客网
题目:
样例输入:
- 3
- 2 1 1 2
- 1 2
- 2 2 1 2
- 1 2
- 1 2
- 3 3 1 2
- 1 2
- 2 3
- 1 3
样例输出:
- Cut Player
- Join Player
- Cut Player
题意:先给定一张无向图,Join和Cut在玩游戏,给定起点和终点,两个人轮流操作,Join每次操作是从当前所在位置走一条边并将该边删除,而Cut每次操作是直接删一条与当前位置相连的一条边,问最后Join能否从起点走到终点,如果能就是Join赢,否则Cut赢
Cut是先手
分析:我们先来看一下Join走到的点x必须要具备什么条件?首先从x到终点的所有路径中的第一条边的个数不能少于2,也就是说我们至少要有两种选择,重边算多条边,不能只算是一条边,因为Cut每次操作都只会砍掉一条边,重边无法一次全部砍掉,所以重边需要当成多条边。因为一旦少于2,那么Cut直接砍掉这条边我们就无法继续向终点走了。而且从x能一步到达的点中是满足上述分析的条件的点的个数也不能小于2,这是显然的,就是一种递归,防止Cut把我们唯一能走的点给切掉,所以必须要满足这个条件我们才能一步步向终点走,但是这样搜索并不是很好处理,因为这样我们是从起点向终点处理我们不知道我们后续的点是否满足我们刚才说的那种性质,所以我们需要从终点向起点方向进行bfs,由于图中可能存在环,所以我们可以标记一个vis,保证每个点只会进入队列一次,定义f[i]为从i出发向终点的路径中满足上述性质的边数,也就是我们从i可以选择的第一条边的数目,我们保证只有具备上述性质的点才会进入队列(一开始的终点除外),也就是f[i]>=2,这样我们最后只需要判断一下起点是否进入过队列即可知道是否可以从起点走到终点,凡是进入队列的x(除终点外)均满足f[x]>=2,那么就会存在一条路径是可以从x到达终点的。由于图中含有重边,所以我们可以直接用邻接矩阵来存图,d[i][j]记录从i到j的连边的个数。有小伙伴可能会问了,那我们为什么不考虑Join的删边操作呢?其实这个原因很简单,因为Join删除的边都是我们已经走过的边,而我们从起点到终点的路径中没必要经过同一个点,否则就会生成环,那么我们可以直接把环给去掉而不会影响答案。
细节见代码:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- using namespace std;
- const int N=103;
- int d[N][N],f[N];
- bool vis[N];
- int n,m,s,t;
- void dp(int t)
- {
- queue<int>q;
- q.push(t);
- while(!q.empty())
- {
- int x=q.front();
- q.pop();
- if(vis[x]) continue;
- vis[x]=true;
- for(int i=1;i<=n;i++)
- {
- if(d[x][i]) f[i]+=d[x][i];
- if(!vis[i]&&f[i]>=2) q.push(i);
- }
- }
- }
- int main()
- {
- int T;
- cin>>T;
- while(T--)
- {
- scanf("%d%d%d%d",&n,&m,&s,&t);
- for(int i=1;i<=n;i++)
- {
- vis[i]=false;f[i]=0;
- for(int j=1;j<=n;j++)
- d[i][j]=0;
- }
- for(int i=1;i<=m;i++)
- {
- int u,v;
- scanf("%d%d",&u,&v);
- d[u][v]++;d[v][u]++;
- }
- dp(t);
- if(vis[s]) puts("Join Player");
- else puts("Cut Player");
- }
- return 0;
- }