2014-2015 ACM-ICPC, Asia Xian Regional Contest Problem H. The Problem to Make You Happy(博弈,记忆化搜索)

题意:

给你n(<=100)个点m(<=n*(n-1))条边的有向简单图,Alice和Bob(两个人都足够聪明)在这个图上玩游戏,两个人轮流沿着有向图走,一次只能走一条边,Bob先走,如果Alice和Bob走到同一个点,或者Bob无法走了,则Bob输,否则Bob赢(Alice永远追不上Bob或者Alice无路可走)。如果Bob能赢输出Yes,否则输出No。

思路:

考虑记忆化搜索。但是由于图并不是DAG,我们只能倒着从已知的状态往前推,从而找出所有的Bob的必败态。

dp[i][j][k]表示Bob在i点,Alice在j点,轮到k(k=0表示该Bob走了,k=1表示该Alice走了)走了时,Bob是否能赢(不能则值为0)。

显然可以找出两种Bob的必败态:

1、该Bob走了,并且无路可走

2、无论该谁走了,Bob和Alice在同一个点

将两种必败态的所有点加入队列,假设当前Bob在x点,Alice在y点,依次判断:

1、如果下一步该Bob走了并且是(Bob的)必败态,那么Alice直接从y的前一个点ny走到y。将得到的新的必败态(x,ny,1)加入队列。

2、如果下一步该Alice走了并且是(Bob的)必败态,那么枚举x的前驱节点nx,如果nx的所有后继状态都是必败态,那么得到新的必败态(nx,y,0)加入队列。

由于nx的所有后继状态都是必败态我们才去判断它,所以可以先记录好每个节点的出度,然后倒着建图,用一个cnt[i][j]表示i的(倒着建图的)后继节点的必败态数量,这样更方便判断。

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(register int i=(a);i<=(b);i++)
#define dep(i,a,b) for(register int i=(a);i>=(b);i--)
using namespace std;
const int maxn=4e2+5;
//const double pi=acos(-1.0);
//const double eps=1e-9;
//const ll mo=1e9+7;
int n,m,k;
int a[maxn],c[maxn];
int ans,tmp;
int flag;
char s[maxn];
bool ok[maxn];
vector<int>vc[maxn],vv;
int dp[maxn][maxn][2];
int cnt[maxn][maxn];
int du[maxn];
template <typename T>
inline void read(T &X)
{
    X=0;int w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    if(w) X=-X;
}
struct node{
    int x,y;
    int z;
    node(){}
    node(int xx,int yy,int zz){
        x=xx;y=yy;z=zz;
    }
};
queue<node>q,p;
int main()
{
    int T,cas=1;
    read(T);
    while(T--)
    {
        read(n);read(m);
        q=p;
        ans=0; flag=0;
        int sb,sa;
        rep(i,1,n) {vc[i].clear();du[i]=0;}
        rep(i,0,n)
        rep(j,0,n)
        rep(k,0,1){
            dp[i][j][k]=-1;
            cnt[i][j]=0;
        }
        rep(i,1,n)
        rep(k,0,1){
            dp[i][i][k]=0;
            q.push(node{i,i,k});
        }
        vv.clear();
        rep(i,1,m){
            int x,y;
            read(x);read(y);
            vc[y].push_back(x);
            du[x]++;
        }
        rep(i,1,n) if(!du[i]){
            vv.push_back(i);
        }
        for(int k=0;k<vv.size();k++){
            int j=vv[k];
            rep(i,1,n){
            if(dp[j][i][0]!=0) {
                dp[j][i][0]=0;
                q.push(node{j,i,0});
            }
            }
        }
        read(sb);read(sa);
        while(!q.empty()){
            node s=q.front();q.pop();
            int x=s.x;
            int y=s.y;
            int z=s.z;
            if(z==0){
                for(int i=0;i<vc[y].size();i++){
                    int j=vc[y][i];
                    if(dp[x][j][1]){
                        dp[x][j][1]=0;
                        q.push(node{x,j,1});
                    }
                }
            }
            else {
                for(int i=0;i<vc[x].size();i++){
                    int j=vc[x][i];
                    if(++cnt[j][y]==du[j])
                    if(dp[j][y][0]){
                        dp[j][y][0]=0;
                        q.push(node{j,y,0});
                    }
                }
            }
        }

        printf("Case #%d: ",cas++);
        if(dp[sb][sa][0]) puts("Yes");
        else puts("No");
    }
    return 0;
}
/*
555
4 6
1 2
2 3
3 2
4 3
3 4
2 1
2 3
*/

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值