题意:
n个坐标上面分别有n棵树,每发射一次枪,可以把一条直线上面的树都消灭掉。
问要消灭到m棵树,最少需要几枪。
思路:
记忆化搜索。原则上每枪最少要消灭2棵树,如果最后一枪下去导致剩下的树小于等于(n-m),则控制最后一枪使结果恰好等于结果。其实发射枪的次数还是不变的。
用一个整数的位表示剩下的树,预处理的过程中,g[i, j]表示与i树和j树在一条直线上面的树。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <algorithm>
using namespace std;
const int MAXN = 20;
const int MAXD = 65600;
int f[MAXD];
int x[MAXN], y[MAXN];
int g[MAXN][MAXN];
int n, m, d;
void init()
{
memset(g, 0, sizeof(g));
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
if (i != j)
for (int k = n - 1; k >= 0; --k)
{
g[i][j] <<= 1;
if((y[j] - y[i]) * (x[k] - x[i]) == (x[j] - x[i]) * (y[k] - y[i]))
++g[i][j];
}
}
int dp(int u)
{
if (f[u] != -1)
return f[u];
int c = 0;
for (int i = 0; i < n; ++i)
if (u & (1<<i))
++c;
if (c <= d)
return f[u] = 0;
if (c == 1)
return f[u] = 1;
int ans = INT_MAX;
for (int i = 0; i < n; ++i)
if (u & (1<<i))
for (int j = i + 1; j < n; ++j)
if (u & (1<<j))
ans = min(ans, dp(u & (~g[i][j])) + 1);
return f[u] = ans;
}
int main()
{
int cases, count = 0;
scanf("%d", &cases);
while (cases--)
{
scanf("%d %d", &n, &m);
for (int i = 0; i < n; ++i)
scanf("%d %d", &x[i], &y[i]);
init();
memset(f, -1, sizeof(f));
d = n - m;
int ans = dp((1<<n) - 1);
printf("Case #%d:\n", ++count);
printf("%d\n", ans);
if (cases)
printf("\n");
}
return 0;
}