题目链接:点击查看
题目大意:两个人正在玩游戏,现在给出 n 个点,两个人轮流操作,每次可以选择任意个贡献的顶点然后删除,最后无法操作的人失败
题目分析:读完题后想到了之前做过的一道题目:点击查看
因为 n 比较小,不难想到状态压缩,所以最朴素的做法就是设 m = 2^n ,直接 m * m 枚举状态然后写 dfs 转移状态就好了,在此之前需要记得预处理一下共线的限制,共线限制也可以利用状态压缩来保存,只需要判断每个二进制状态中所有为 1 的位置是否共线即可,在处理共线限制时,因为时间复杂度计算的比较充裕,所以我直接选择了 2^n * n^3 来预处理(正解好像是 2^n *n 的时间复杂度预处理的),原理就是:如果某些顶点贡献,那么任意三个顶点都共线,这样 2^n 枚举状态,n^3 枚举三个顶点判断是否贡献即可,这样总的时间复杂度就是 4^n ,还好出题人没有卡时间,1.2s 水过去了
但显然这样肯定不是正解,赛后补题时,仔细分析了一下,发现:
假设当前是状态 x 向状态 y 进行转移,那么对于任意一个位置来说,只会有三种情况:
- x = 0 , y = 0,当前位置之前就被删除
- x = 0 , y = 1,本轮操作删除当前位置
- x = 1 , y = 1,当前位置本轮也不删除
注意到没有,当 x = 1 且 y = 0 时的这个状态是不合法的,所以我们完全可以通过搜索进行剪枝将时间复杂度降低到 3^n,这也正需要一个小知识点:如何枚举子集的子集
当然只需要两行代码就可以实现
for (int S=1; S<(1<<n); S++)
for (int S0=S;S0;S0=(S0-1)&S)
然后实现就非常简单了
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=15;
int n;
LL x[N],y[N];
bool can[(1<<N)+100];
int dp[(1<<N)+100];
void init()
{
auto check=[&](int i,int j,int k)
{
return (y[i]-y[j])*(x[i]-x[k])==(y[i]-y[k])*(x[i]-x[j]);
};
for(int t=1;t<1<<n;t++)
{
can[t]=true;
for(int i=0;i<n;i++)
{
if((t&(1<<i))==0)
continue;
for(int j=i+1;j<n;j++)
{
if((t&(1<<j))==0)
continue;
for(int k=j+1;k<n;k++)
{
if((t&(1<<k))==0)
continue;
if(!check(i,j,k))
can[t]=false;
}
}
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("input.txt","r",stdin);
// freopen("output.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%lld%lld",x+i,y+i);
init();
for(int i=0;i<1<<n;i++)
for(int j=i;j;j=(j-1)&i)
if(can[j]&&!dp[i^j])
dp[i]=true;
if(dp[(1<<n)-1])
puts("zyh");
else
puts("fzj");
return 0;
}