题目链接:http://lightoj.com/volume_showproblem.php?problem=1018
题意分析:平面上有不超过N个点,现在可以任意方向划直线将它们划去,问:最少要划几次可以把所有的点划去?
解题思路:我们可以使用集合S表示:有哪些点还没有被划掉,然后转移 dp[s] = min(dp[s &(~line[i][j])]) + 1;这里涉及到line[i][j]的处理,它代表的是在i点和j点构成的直线上一共有几个点,需要预先处理。边界条件就是S中集合元素 >0 && <= 2时,肯定是1。元素空时就是0了。
个人感受:一开始想到的是用s代表当前点没被划过的集合,然后卡在了怎么转移上,想着选两个点遍历着判断这两个点所在直线有几个点,感觉炒鸡麻烦。然后就是翻了别人的题解,看到了预处理这字样,豁然开朗啊~
具体代码如下:
#include<iostream> #include<cctype> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<algorithm> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 20; int x[MAXN], y[MAXN], line[MAXN][MAXN], n, dp[1 << 16]; void init() { memset(line, 0, sizeof line); memset(dp, 0x3f, sizeof dp); dp[0] = 0; for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) { line[i][j] = (1 << i) | (1 << j); int dx = x[j] - x[i], dy = y[j] - y[i]; for (int k = j + 1; k < n; ++k) { int dx2 = x[k] - x[i], dy2 = y[k] - y[i]; //这里我用了向量平行的条件 if (dx2 * dy == dy2 * dx) line[i][j] |= (1 << k); } line[j][i] = line[i][j]; } } int dfs(int s) { int& ret = dp[s]; if (dp[s] < INF) return ret; int num = __builtin_popcount(s); //计算s中有几个1.很好用的函数 if (num <= 2) return ret = 1; int i = 0; while (!(s & (1 << i))) ++i; //找出第一个没被删除的点 for (int j = i + 1; j < n; ++j) //和其它点进行匹配。这里匹配次序是不会影响最终结果的 { if (s & (1 << j)) { ret = min(ret, dfs(s&(~line[i][j])) + 1); } } return ret; } int main() { int t; for (int kase = scanf("%d", &t); kase <= t; ++kase) { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d%d", x+i, y+i); init(); printf("Case %d: %d\n", kase, dfs((1 << n) - 1)); } return 0; }
版权声明:欢迎转载(^ω^)~不过转载请注明原文出处:http://blog.csdn.net/catglory ?(????)
时间: 2024-10-26 02:31:13