这题属于状态压缩DP中比较基础的一题,经过仔细分析后我们发现此题虽然坐标范围较大,但是点比较少最多才16个很容易想到用状态压缩。dp[x]表示当前树的状态最少要转移的次数(砍的次数)。具体状态转移由于他状态转移的顺序比较乱所以用的是记忆化搜索。起始状态是(1<<n)-1,边界是已有树的数目小于n-m(kk)返回0,只多出一棵树是返回1。
代码如下:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 using namespace std; 5 6 int n, kk, ans, dp[1000000]; //状态压缩数组一共2^16个状态 7 8 typedef struct Point{ 9 int x, y; 10 }P; 11 12 P p[20]; 13 14 //判断三点是否共线 15 bool isline(int i,int j,int k) 16 { 17 int a=p[i].x-p[j].x,b=p[i].y-p[j].y; 18 int c=p[j].x-p[k].x,d=p[j].y-p[k].y; 19 return a*d==b*c; 20 } 21 22 23 int Judge(int x) 24 { 25 int ret = 0; 26 while(x){ 27 ret+=(x&1); 28 x>>=1; 29 } 30 return ret; 31 } 32 33 void init() 34 { 35 scanf("%d%d", &n, &kk); 36 for(int i=0; i<n; i++){ 37 scanf("%d%d", &p[i].x, &p[i].y); 38 } 39 ans = 20; 40 memset(dp, -1, sizeof dp); 41 } 42 43 int solve(int x) 44 { 45 if(dp[x]!=-1) return dp[x]; 46 if(Judge(x)<=n-kk) return 0; 47 if(Judge(x)==n-kk+1) return dp[x] = 1; 48 49 int ret = 20; 50 for(int i=0; i<n; i++) 51 if((1<<i)&x){ 52 for(int j=i+1; j<n; j++) 53 { 54 if((1<<j)&x){ //两棵树都在枚举直线 55 int tx = x; 56 for(int k=0; k<n; k++){ 57 if((1<<k)&x && isline(i, j, k)){ 58 tx -= (1<<k); 59 } 60 61 } 62 ret = min(ret, solve(tx)+1); 63 } 64 } 65 } 66 return dp[x] = ret; 67 } 68 69 int main() 70 { 71 //freopen("in.txt","r", stdin); 72 //freopen("out.txt","w", stdout); 73 74 75 int T, cnt; 76 scanf("%d", &T); 77 for(cnt=1; cnt<=T; cnt++) 78 { 79 init(); 80 ans = solve((1<<n)-1); 81 printf("Case #%d: ", cnt); 82 printf("%d ", ans); 83 if(cnt!=T) printf(" "); 84 } 85 return 0; 86 }