杭电多校 I love playing games

题目

题目大意

给定一个无向图,每一轮,两人从x,y两点有先后地选择不移动或移动到相邻点(两人都进行选择才算一轮)。当最后一轮结束时,若两人都达到z点或都到不了z点,则平局输出2。若先手的人到了,后手未到输出1,反之输出3。两人除了起点和终点外,不能处在相同节点上。

题解

通过BFS找到z到x,y的最短路径分别为dis[x],dis[y]。

  • 若dis[x]!=dis[y]则谁路劲短谁获胜。
  • 若dis[x]==dis[y],则先手必不败,可能平局。
简单证明:

一 两人最短路除z点外无交点

  1. dis[x]!=dis[y]
    i.若先手的最短路小于后手,易得先手必胜。
    ii.若后手的最短路小于先手,考虑极限情况,dis[x]=dis[y]+1。每轮两人都贪心地向z点移动(因为最短路无交点),dis[x]–,dis[y]–。可以看到,每一轮结束时,dis[x]一定大于dis[y],所以dis[y]能先于归零,即后手能先到达终点,后手必胜。
  2. dis[x]==dis[y]
    两者最短路无交点,所以当最后一轮结束时,两者同时到达z点,平局。

二 两人最短路除z点有交点

  1. dis[x]!=dis[y]
    假设dis[x]<dis[y],且两者的交点为k。若忽略交点k的影响,可以得出先手必胜的结论。能想到的后手若想取胜的方法就是:在先手还未经过k点前,在任意一轮结束时先于先手到达k点后停留在k点,当先手到达k的前一点时就会由于后手已经在k点,会选择停留直到后手走开,由此后手转化为先手就能取胜。**但是后手必定不能先于先手到达交点k。**因为交点k到z点的最短路的值dis[k]是唯一确定的,dis[x]=dis[k]+x到k的最短路程。dis[y]=dis[y]+y到k的最短路程。若后手能先于先手到达k点,就会得到dis[x]>dis[y]的结论。所以不存在这种情况,当最短路不同时任然是谁的最短短谁取胜。
  2. dis[x]==dis[y]
    同理,忽略交点k的影响,就可以得到平局的结论。现在考虑k点能怎样影响胜负。考虑极限情况,只有唯一交点k,先手和后手都得从该路径到达z点,因为先手能先到达k点,后手也得通过k点,就必须等待先手走开,则先手胜,否则平局。推到一般情况,存在多个交点,可以得到,若后手的所有最短路径中都有k点,并且先手存在一条最短路劲也有k点则先手胜,否则平局。(题目里的样例第一个就该情况)

标程 AC代码

虽然最短路的值是唯一确定的,但是可能存在多条这样的路径。不方便通过交点去查看,就按标程里的DP来。

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int N=2e3+5;
const int inf=1e9;

struct node{
    int a,b,c;
};

int dp[N][N][2];
int n,m,x,y,z;
int dis[N];
vector<int> pre[N];
vector<int> e[N];
vector<node> v;

int f(int x,int y,int z,int k)
{
    if((k==0)&&(x==z||y==z))//k==0限制判断都得是每轮结束
    {
        if(x==z&&y==z)
            return 2;
        return 1;//不是平局就是先手胜
    }
    if(dp[x][y][k])
        return dp[x][y][k];
    int kk;
    /*
    两人最短大小相同,先手必不败
    如果k==0则现在由先手走,先手就会选择使自己获胜的方案,若不存在,则平局
    如果k==1则现在由后手走,后手只能尽量选择平局的方案,若不存在,则先手胜
    */
    if(k==0)
    {
        kk=2;
        for(int i=0;i<pre[x].size();i++)
        {
            int u=pre[x][i];
            if(u!=y||u==z)
            {
                int tt=f(u,y,z,k^1);
                if(tt==1)//若存在一种方案使先手获胜,则选择该种方案。
                	kk=1;
            }
        }
        dp[x][y][k]=kk;
    }
    else
    {
        kk=1;
        for(int i=0;i<pre[y].size();i++)
        {
            int u=pre[y][i];
            if(u!=x||u==z)
            {
                int tt=f(x,u,z,k^1);
                if(tt==2)//若存在一种方案使平局,则选择该种方案。
                	kk=2;
            }
        }
        dp[x][y][k]=kk;
    }
    v.push_back({x,y,k});
    return dp[x][y][k];
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        for(int i=1;i<=n;i++)
            e[i].clear();
        scanf("%d%d",&n,&m);
        scanf("%d%d%d",&x,&y,&z);
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        
        for(int i=1;i<=n;i++)
            dis[i]=inf;
        dis[z]=0;
        queue<int> que;
        que.push(z);
        while(!que.empty())//BFS求最短路
        {
            int x=que.front();
            que.pop();
            for(int i=0;i<e[x].size();i++)
            {
                int v=e[x][i];
                if(dis[v]==inf)
                {
                    dis[v]=dis[x]+1;
                    que.push(v);
                }
            }
        }
        
        if(dis[x]!=dis[y])
        {
            printf("%d\n",dis[x]>dis[y]?3:1);
            continue;
        }
        
        if(dis[x]==inf)
        {
            printf("2\n");
            continue;
        }
        
        for(int i=1;i<=n;i++)//找出所有的最短路径
        {
            for(int j=0;j<e[i].size();j++)
                if(dis[i]==dis[e[i][j]]+1)
                    pre[i].push_back(e[i][j]);
        }
        printf("%d\n",f(x,y,z,0));
        for(int i=1;i<=n;i++)
            pre[i].clear();
        for(int i=0;i<v.size();i++)
            dp[v[i].a][v[i].b][v[i].c]=0;//不能使用memset会T
        v.clear();
    }
    return 0;        
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值