OI - Game(HDU) | Lucky_Glass's Blog
0%

OI - Game(HDU)

其实我觉得这道题的处理挺巧妙的……想了好久才想出来

反正还是被 tly 给秒了 wtcl :(


# 题面

> HDU 6850

点击展开/折叠题面翻译

在二维平面上给定 $n$ 个点,一开始有一个石头在点 $1$。

先后手轮流操作,每次操作将石头移动到一个之前没有到过的点,且要求移动的欧几里得距离严格大于上一次移动(如果是第一次移动则视“上一次移动”距离为 $0$)。

不能移动的人输,问先手是否有必胜策略。


# 解析

发现很难找到必胜策略,但是可以很明显地找到两个结论,称相距最远的两个点为“直径”(可能有多个):

  1. 如果点 $1$ 是直径的一个端点,则先手必胜,可以直接走直径,后手就不可能找到更远的点;
  2. 若点 $1$ 不在直径上,则先后手在任意时刻都“不愿意”走到直径上的点,若走到直径上的点,对手就可以走直径从而获胜。

先判断第一个结论,然后再看第二个结论。既然先后手的最优策略都不会想往直径上的点走,那就是说我们把当前直径上的点都删去,对先后手的最优策略以及NP状态都不会影响。

于是删去直径上的点(注意如果有多个相等的直径,要一起删去),就得到了一个子问题。对这个子问题再求直径,仍然判断两个结论,若满足结论2,则删去当前直径继续得到子问题……

什么时候会结束?

  1. 点 $1$ 变成当前直径的端点,先手必胜;
  2. 删的只剩 $1$ 这个点,后手必胜;

# 源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

inline int Ri(){
register int r=0,c=getchar(),b=1;
while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
while('0'<=c && c<='9') r=(r<<1)+(r<<3)+c-'0',c=getchar();
return r*b;
}

const int N=2005;
typedef long long ll;

int cas,nedg,n,nstk;
ll pnt[N][2];
int stk[N*2];
bool del[N];
struct EDGE{
ll dis;
int x,y;
EDGE(){}
EDGE(int X,int Y){
x=X,y=Y;
dis=(pnt[X][0]-pnt[Y][0])*(pnt[X][0]-pnt[Y][0])+(pnt[X][1]-pnt[Y][1])*(pnt[X][1]-pnt[Y][1]);
}
friend bool operator <(const EDGE &a,const EDGE &b){return a.dis>b.dis;}
}edg[N*N];

int main(){
cas=Ri();
while(cas--){
n=Ri();
for(int i=1;i<=n;i++) pnt[i][0]=Ri(),pnt[i][1]=Ri(),del[i]=false;
nedg=0;
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
edg[++nedg]=EDGE(i,j);
sort(edg+1,edg+1+nedg);
for(int i=1,j;i<=nedg;i){
for(j=i;j<=nedg && edg[i].dis==edg[j].dis;j++){
if(del[edg[j].x] || del[edg[j].y]) continue;
stk[++nstk]=edg[j].x,stk[++nstk]=edg[j].y;
}
i=j;
while(nstk) del[stk[nstk--]]=true;
if(del[1]) break;
}
if(del[1]) printf("YES\n");
else printf("NO\n");
}
return 0;
}

THE END

Thanks for reading!

> Linked 夏夜空-网易云