状态DP,以dp[i]记录砍掉的树的状态为 i 时的最少发射激光数,对于每个状态考虑将当前状态下未被砍的树中选一颗来砍,则考虑两种情况,一个是这颗树需要单独一束激光来砍,另一个就是这棵树和其他已经被砍的某一颗树形成一条线,而在这条线上的点都可以被砍掉,所以求砍这个方向的前一个状态时需要将在这条线上的点都减掉。
具体说明看代码:
#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <map>
#include <vector>
#include <cmath>
#include <stack>
#include <queue>
#include <cstdlib>
#include <algorithm>
using namespace std;
//typedef __int64 int64;
typedef long long ll;
#define M 100005
#define max_inf 0x7f7f7f7f
#define min_inf 0x80808080
#define mod 1000000007
int n , m , x[25] , y[25];
int dp[1<<22];
bool Judge(int k)//判断当前状态砍掉的树时多少棵树
{
int num = 0;
while (k)
{
num += (k&1);
k >>= 1;
}
return m <= num;
}
void Solve()
{
int i , j , k , l , up = 1<<n , tp[25] , ans = max_inf;
memset(dp , max_inf , sizeof dp);
dp[0] = 0;
for (i = 0 ; i < up ; i++)
{
for (j = 0 ; j < n ; j++)
{
if (i & (1<<j))continue;//若第j颗树已经在状态i中,则继续
int next = i+(1<<j);//砍掉这棵树后的下一个状态
dp[next] = min(dp[next],dp[i]+1);
int temp = i , cnt = 0;
for (k = 0 ; k < n ; k++)//记录状态i里,被砍掉的树
{
if (temp&1)tp[cnt++] = k;
temp >>= 1;
}
for (k = 0 ; k < cnt ; k++)//确定一个方向
{
temp = i;
for (l = 0 ; l < cnt ; l++)
{
//将这个方向上的点都减掉
if ( (y[j]-y[tp[k]])*(x[tp[l]]-x[j]) == (x[j]-x[tp[k]])*(y[tp[l]]-y[j]) ) temp -= (1<<tp[l]);
}
dp[next] = min(dp[next] , dp[temp]+1);
}
}
if (Judge(i))ans = min(ans,dp[i]);
}
printf("%d\n",ans);
}
int main()
{
int t , i , tcase = 1;
scanf("%d",&t);
while (t--)
{
scanf("%d%d",&n,&m);
for (i = 0 ; i < n ; i++)scanf("%d%d",x+i,y+i);
if (tcase > 1)printf("\n");
printf("Case #%d:\n",tcase++);
Solve();
}
return 0;
}