题意:一个网格内(5*5)有n个点,问最少多少条线可以把这些点划掉。

题解:以前做过这种类型的题,先把共线点用二进制数表示出来,然后dp的时候传入全1的状态,dp函数内枚举两点消除其他共线点,最后得到最小值,但这道题用这种方法会超时。。。所以要有新的解法,根据题解。。。。。需要添加两个数组nbits[1 << 20]和first[1 << 20],分别表示存储当前状态有几个1和然后当前状态的第一个0位的位置,dp时有两个参数s和cnt,初始都是0,表示刚开始点都没有被划掉,且线的数量为0,然后用f[1 << 20]和g[1 << 20]分别表示当前状态是否被扫描过和当前状态最优的划线值,dp主要思想是从当前状态第一个0位开始(第一个未被划掉的点),枚举后面的每一个点(位是1也有可能,已经划掉也不一定是最优),划掉共线点找到最优解,一堆剪枝,(1)如果当前划线数量cnt超过g[s],(2)或者cnt > res,都可以return,(3)然后如果剩余1位数量<=2,直接res = cnt + 1。最后注意的是输入点可能会重复,需要去重点。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 21;
struct P {
	int x, y;
}p[N];
int f[1 << 20];//判断当前状态是否扫描过
int line[N][N];//判断和i、j共线的点
int flag[6][6];//判断点(x, y)是否出现过
int g[1 << 20];//判断当前状态有多少条线划过(最优)
int first[1 << 20];//判断当前状态第一个0的位置
int nbits[1 << 20];//计算当前状态有多少个1
int n, res, test;

bool judge(int i, int j, int k) {
	return (p[j].y - p[i].y) * (p[k].x - p[i].x) == (p[k].y - p[i].y) * (p[j].x - p[i].x) ? 1 : 0;
}

void dp(int s, int cnt) {
	if (f[s] == test && g[s] <= cnt)
		return;
	f[s] = test;
	g[s] = cnt;
	if (cnt >= res)
		return;
	if (nbits[s] == n) {
		res = cnt;
		return;
	}
	if (n - nbits[s] <= 2) {
		res = cnt + 1;
		return;
	}
	for (int i = first[s] + 1; i < n; i++)
		dp(s | line[first[s]][i], cnt + 1);
}

int main() {
	for (int i = 0; i < (1 << 20); i++) {
		nbits[i] = 0;
		first[i] = -1;
		for (int j = 0; j < 20; j++) {
			if (1 & (i >> j))
				nbits[i]++;
			else if (first[i] == -1)
				first[i] = j;
		}
	}
	memset(f, 0, sizeof(f));
	int t, cas = 1;
	scanf("%d", &t);
	for (int ii = 1; ii <= t; ii++) {
		memset(flag, 0, sizeof(flag));
		res = 6;
		test = ii;
		scanf("%d", &n);
		int a, b;
		for (int i = 0; i < n; i++) {
			scanf("%d%d", &a, &b);
			flag[a][b] = 1;
		}
		n = 0;
		for (int i = 0; i < 6; i++)
			for (int j = 0; j < 6; j++)
				if (flag[i][j]) {
					p[n].x = i;
					p[n++].y = j;
				}
		for (int i = 0; i < n; i++)
			for (int j = i + 1; j < n; j++) {
				int s = (1 << i) | (1 << j);
				for (int k = 0; k < n; k++) {
					if (k == i || k == j)
						continue;
					if (judge(i, j, k))
						s |= (1 << k); 
				}
				line[i][j] = s;
			}
		dp(0, 0);
		printf("Case %d: %d\n", cas++, res);
	}
	return 0;
}