SGU 520 Fire in the Country 博弈SG函数(或者YY一下)

题目大意:
现在给出一个有着n个顶点m条边的图,边为双向边, 现在两个人轮流操作机器人,机器人从点1开始走,两个人轮流控制机器人走向与当前点相连的点,两个人每次操作机器人走的同时,火势将会向外蔓延,每操作一次之后火势就向与当前着火点相连的点蔓延,机器人在谁操作之后被火烧到谁就输,比如样例:
Nikolay先手从1走到3,当天晚上1着火,然后Vladimir只能从3走到1,当天晚上1是着火点所以Vladimir输了,输出Vladimir


大致思路:
这题比赛的时候想的是一个YY的做法:
我定义P点:一个点是P点当且仅当谁控制机器人走到这个点谁就赢
相反的是N点:一个点是N点表示谁控制机器人走到这个点谁就输
由于火势是不停蔓延的,那么控制机器人行动时不能回头,这样即使图中有环,也不可能往回走,可以先BFS遍历一下找到点的层级( 即到达该点的步数 ) 这样我们就只能控制机器人从层级低的点走到层级高的点,这样原图可以变成一个无环有向图,图的边来自于原图中层级低的点指向层级高的点且在原图中连通
我们考虑父亲结点root和子节点son[ i ] ( 这里就是拓扑序的意思,方编写我就当做树的层级吧..虽然可能不是树...) ,当父亲节点没有子节点时,一定是 P 点,因为下一个人已经无路可走了
当父亲节点有子节点时,如果子节点中有P点,那么父亲节点是N点( 博弈中的小策略 )也很容易明白因为代打这个父亲节点后,下一个人一定会选择走到P点这样走到这个父亲节点的人就输了,也就是说只需要判断父亲节点的子节点中有无P点即可判断父亲节点是P点还是N点
这样dfs一下找到节点1的值是P还是N即可,是N则Vladimir赔钱,否则Nikolay赔钱


比赛之后学了一些图上的博弈,发现这题可以用Sprague-Grundy函数来解和之前一样BFS建好新的无环有向图之后,用dfs迭代求SG函数的值
当SG[ 1 ] == 0 时,Nikolay赔钱,SG[ 1 ] != 0时,Vladimir赔钱
这里附上SG函数解法的代码(相比上一种比赛时YY出来的方案感觉还是好一点)


题解写一半发现CSDN维护了..我擦...


Result  :  Accepted     Memory  :  322 KB     Time  :  15 ms

/*
 * Author: Gatevin
 * Created Time:  2014/9/2 22:10:56
 * File Name: C.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
#define pb(x) push_back(x)
#define clr(x) memset(x, 0, sizeof(x))
int n,m;
int deep[1010];//到达对应的点需要走的步数
bool vis[1010];//用了两次,都是用来记忆化
vector <int> G[1010];//原图
vector <int> F[1010];//删边之后的博弈求SG函数用图
queue <int> q;
int SG[1010];

void bfs()
{
    clr(vis);
    vis[1] = 1;
    q.push(1);
    while(!q.empty())
    {
        for(unsigned int i = 0; i < G[q.front()].size(); i++)
        {
            if(!vis[G[q.front()][i]])
            {
                deep[G[q.front()][i]] = deep[q.front()] + 1;
                vis[G[q.front()][i]] = 1;
                q.push(G[q.front()][i]);
            }
        }
        q.pop();
    }
    return;
}

void build()
{
    for(int i = 1; i <= n; i++)
    {
        for(unsigned int j = 0; j < G[i].size(); j++)
        {
            if(deep[i] < deep[G[i][j]])
            {
                F[i].pb(G[i][j]);
            }
        }
    }
    return;
}

bool check(int val, int root)//求mex()
{
    for(unsigned int i = 0; i < F[root].size(); i++)
    {
        if(val == SG[F[root][i]]) return true;
    }
    return false;
}

void dfs(int root)//递归求解SG函数
{
    if(F[root].empty())
    {
        SG[root] = 0;
        return;
    }
    if(vis[root]) return;
    for(unsigned int i = 0; i < F[root].size(); i++)
    {
        dfs(F[root][i]);
    }
    int cnt = 0;
    while(check(cnt, root))
    {
        cnt++;
    }
    SG[root] = cnt;
    vis[root] = 1;
    return;
}

int main()
{
    scanf("%d %d", &n, &m);
    int tx,ty;
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d", &tx, &ty);
        G[tx].pb(ty);
        G[ty].pb(tx);
    }
    bfs();
    build();
    clr(vis);
    dfs(1);
    if(SG[1] != 0)
    {
        printf("Vladimir\n");
    }
    else
    {
        printf("Nikolay\n");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值