ZOJ 1031 Square Destroyer

 

解题思路:

判断是否存在完整的正方形

 

下面是正方形边编号的计算公式

边长l(1~n)

层数i(0~n-l)

序号j(0~n-l)

层次s(0~l+1)

   如果s==0

       序号t(0~l-1)

           边为(2*n+1)*i+j+t

   如果s==l+1

       序号t(0~l-1)

           边为(2*n+1)*i+j+s*(2*n+1)+t

   其余

       边为(2*n+1)*i+j+(s-1)*(n+1)+s*n,

           (2*n+1)*i+j+(s-1)*(n+1)+s*n+l

 

剪枝

1.state&opt[i]==0 ==> ==0表示后面可以全消,如果不为0,表示即使后面全部加上也消不掉,所以没必要进行

2.state&set[i]<state ==> state表示剩余正方形,一旦减小,证明i可以添加,如果不减小,证明添加无用,所以没必要进行

3.cur+1+state_count/count[i + 1]<min ==> 选择i之后如果数量的下限大于min,则无需继续进行了

 

贪心

按照对state消元的数量降序排列

非常奇怪的是,如果不对state处理,单单比较set,那么必须升序排列

 

搜索时,最为关键的是如何减少递归的次数,也就是充分的利用必要信息来剪枝(可行性+最优性)(充分条件是拿来构造解的),剪枝利用到的信息不一定要一致,相反,所有的必要条件都可以拿来剪枝

 

优化时,注意使用gprof来测量程序,而不是主观臆断耗时

 

 

源码如下:

 

#include <stdio.h> #include <string.h> #include <stdlib.h> #define MAX_NUM 5 int min; unsigned long long set[2 * MAX_NUM * (MAX_NUM + 1)]; int set_num; unsigned long long opt[2 * MAX_NUM * (MAX_NUM + 1)]; unsigned long long state; int count[2 * MAX_NUM * (MAX_NUM + 1)]; int shift[MAX_NUM] = {0, 25, 41, 50, 54}; int minMax[] = {0, 1, 3, 6, 9, 12}; const int lut[256] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 }; //最快速的1个数统计 int count1(unsigned long long x) { int i, ret = 0; for (i = 0; i < 64; i += 8) ret +=lut[(x >> i) & 0xff]; return ret; } int ullCmp(const void* a, const void* b) { unsigned long long* ull_a = (unsigned long long*)a; unsigned long long* ull_b = (unsigned long long*)b; return count1(state&*ull_b) - count1(state&*ull_a); } void preSet(int n) { state = 0; int l, i, j, s, t; for (l = 1; l <= n; l++) { for (i = 0; i <= n - l; i++) { for (j = 0; j <= n - l; j++) { state |= (unsigned long long)1 << (i * (n - l + 1) + j + shift[l - 1]); for (s = 0; s <= l + 1; s++) { if (s == 0) { for (t = 0; t <= l - 1; t++) set[(2 * n + 1) * i + j + t] &= ~((unsigned long long)1 << (i * (n - l + 1) + j + shift[l - 1])); } else if (s == l + 1) { for (t = 0; t <= l - 1; t++) set[(2 * n + 1) * (i + l) + j + t] &= ~((unsigned long long)1 << (i * (n - l + 1) + j + shift[l - 1])); } else { set[(2 * n + 1) * (i + s - 1) + j + n] &= ~((unsigned long long)1 << (i * (n - l + 1) + j + shift[l - 1])); set[(2 * n + 1) * (i + s - 1) + j + n + l] &= ~((unsigned long long)1 << (i * (n - l + 1) + j + shift[l - 1])); } } } } } } void init(int n) { int i, j; for (i = 0; i < 2 * n * (n + 1); i++) { for (j = 0; j < 2 * n * (n + 1) && opt[i] == 0; j++) { if (i != j && opt[j] == 0) { if (set[i] == set[j] || ((set[i] & ~set[j]) == 0 && (set[j] & ~set[i]) != 0)) opt[j] = 1; else if ((set[j] & ~set[i]) == 0 && (set[i] & ~set[j]) != 0) opt[i] = 1; } } } set_num = 0; for (i = 0; i < 2 * n *(n + 1); i++) { if (opt[i] == 0) set[set_num++] = set[i]; } qsort(set, set_num, sizeof (unsigned long long), ullCmp); opt[set_num - 1] = set[set_num - 1]; for (i = set_num - 2; i >= 0; i--) opt[i] = set[i] & opt[i + 1]; //记录count,就无需每次递归都要计算 for (i = 0; i < set_num; i++) count[i] = count1(~set[i]); count[set_num] = 1; //为了处理边界情况 } void dfs(int s, int cur, int n, unsigned long long state) { if (state == 0) { if (min > cur) min = cur; return; } if (cur >= min - 1) return; int state_count = count1(state); int i; //三种约束 //1.位置应该在正方形内 //2.该位置可行(可行性剪枝) //3.该位置的数量下限<min(最优性剪枝) for (i = s; i < set_num && (state & opt[i]) == 0 && cur + 1 + state_count / count[i + 1] < min; i++) { if ((state & set[i]) < state) dfs(i + 1, cur + 1, n, state & set[i]); } } int main() { /* freopen("test.txt", "r", stdin); */ int case_num; int n, k; int m; int i; scanf("%d", &case_num); while (case_num-- > 0) { memset(opt, 0, sizeof (opt)); memset(set, -1, sizeof (set)); scanf("%d", &n); preSet(n); scanf("%d", &k); for (i = 0; i < k; i++) { scanf("%d", &m); opt[m - 1] = 1; state &= set[m - 1]; } init(n); min = minMax[n]; dfs(0, 0, n, state); printf("%d/n", min); } return 0; }  

你可能感兴趣的:(ZOJ 1031 Square Destroyer)