考虑枚举一个怪物
x
x
x能不能被石头
y
y
y射到,那么我们发现
x
x
x到
y
y
y之间连线上的其他怪物要先被干掉,继续搜索下去,但是胡乱爆搜的话很不可过。
有一个神秘的做法,我们给每个怪物枚举射过来的
k
!
k!
k!种排列,按bfs或dfs的顺序扩展要干掉的怪物,每搜到一个怪物的时候就钦定由排列对应位置上的石头射到它,如果扩展出来的石头数目不超过
k
k
k就合法。
这样为什么是对的呢?注意到如果存在一个合法的解,对它搜索的话一定会有某个扩展顺序,那么当枚举到那个扩展顺序对应的排列时就恰好有解了。
预处理的时候暴力即可,判定可以用叉积和点积看看。时间复杂度
O
(
n
2
k
+
n
k
!
k
)
\mathcal O(n^2k+nk!k)
O(n2k+nk!k)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Point {
ll x,y;
Point() {}
Point(ll a,ll b):x(a),y(b) {}
Point operator - (Point b) {return Point(x-b.x,y-b.y);}
};
inline ll cross(Point x,Point y) {
return x.x*y.y-x.y*y.x;
}
inline ll dot(Point x,Point y) {
return x.x*y.x+x.y*y.y;
}
bool inside(Point x,Point y,Point z) {
return !cross(x-z,y-z)&&dot(x-z,y-z)<0;
}
vector <int> vt[1005][10];
int vis[1005],vis_cnt;
int per[10];
int q[10];
bool bfs(int s,int k) {
vis_cnt++;
int l=1,r=0;
q[++r]=s;vis[s]=vis_cnt;
while (l<=r) {
int x=q[l],v=per[l];
l++;
for(int i=0;i<vt[x][v].size();i++)
if (vis[vt[x][v][i]]<vis_cnt) {
int u=vt[x][v][i];
vis[u]=vis_cnt;
q[++r]=u;
if (r>k) return 0;
}
}
return 1;
}
Point a[10],b[1005];
int main() {
int k,n;
scanf("%d%d",&k,&n);
for(int i=1;i<=k;i++) {
int x,y;
scanf("%d%d",&x,&y);
a[i]=Point(x,y);
}
for(int i=1;i<=n;i++) {
int x,y;
scanf("%d%d",&x,&y);
b[i]=Point(x,y);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
for(int l=1;l<=n;l++)
if (inside(a[j],b[i],b[l])) vt[i][j].push_back(l);
int ans=0;
for(int i=1;i<=n;i++) {
bool ok=0;
for(int j=1;j<=k;j++) per[j]=j;
do {
if (bfs(i,k)) {
ok=1;
break;
}
} while (next_permutation(per+1,per+k+1));
ans+=ok;
}
printf("%d\n",ans);
return 0;
}