【分析】
看到题目的范围时,我就在想这肯定是状压dp,肯定是定义dp[i][j]表示此时在i,取得点的集合为j。
那么问题就来了,有这些点,我怎么来判断从i是否能到j的补集的点呢?接下来我就在纸上画了图。
最后我发现,如果x想要到y,那么在圆上x顺时针到y或x逆时针到y一定都要有点。(至于为什么,我怎么知道,实践出真知)。
那么问题就解决了!
【代码】
#include <bits/stdc++.h>
using namespace std;
#define M 18
#define ll long long
ll dp[M][1<<M];
bool mk[M];
int a[M];
int hav;
int n,m;
bool chk(int now,int to,int si){
int cur;
bool l=0,r=0;
cur=(to+1)%n;//我这个写法比较神奇,这样now到to就不用逆时针旋转了,to只需要顺时针到now,本质是一样的,只是不需要判一些东西。
while(cur!=now){
if(mk[cur]||si&(1<<cur)){
l=1;
break;
}
else cur=(cur+1)%n;
}
cur=(now+1)%n;
while(cur!=to){
if(mk[cur]||si&(1<<cur)){
r=1;
break;
}
else cur=(cur+1)%n;
}
return l&&r;
}
ll DP(int p,int si,int cnt){
ll &res=dp[p][si];
if(res)return res;
if(cnt==n)return res=chk(p,a[1],si);
res=0;
for(int i=0;i<n;i++){
if(i==p||mk[i]||(si&(1<<i)))continue;
if(chk(p,i,si))res+=DP(i,si|(1<<i),cnt+1);
}
return res;
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
a[i]--;
mk[a[i]]=1;
hav+=(1<<a[i]);
}
cout<<DP(a[m],hav,m)<<endl;
return 0;
}