题目大意
给出一个由2*S*(S+1)构成的S*S大小的火柴格。火柴可以构成1x1,2x2...SxS大小的方格。其中已经拿走了几个火柴,问最少再拿走几个火柴可以使得这些火柴无法构成任何一个方格。
题目分析
考虑每个火柴被几个方格占用,这样似乎可以使用贪心算法来解,每次都选择当前剩余的火柴中被完整的方格占用次数最多的那根火柴。理论上感觉可以,具体没去实现。。。
本题,采用的是搜索+剪枝来实现。需要做的是保存每个搜索节点的状态,以及通过合理的记录数据,对状态进行推演。
这里状态为:当前需要被拆除的火柴序号(match_index,可以拆除或者不拆除)+当前剩余的完整的方格的数目(left_square_num)+
当前已经拆除的火柴数目(taken_num,可以用于最优化剪枝)。
而记录数据可以为:火柴i是否位于方块j中 gMatchInSquare[i][j]. 方块s中最大的火柴序号 gMaxMatchInSquare[s](用于剪枝)。
这样,使用最优化剪枝,DFS搜索。剪枝:
(1)对于当前节点,若taken_num > gMinTakenNum,则剪枝返回;
(2)如果火柴 match_index 不存在任何一个剩余的完整的方块中,则不必拆除match_index,即剪枝拆除match_index的情况;
(3)如果火柴 match_index 是当前剩余的某个完整方块的构成火柴的最大的序号,则必须进行拆除(因为,对于火柴是按照序号从小到大进行递归搜索,如果match_index为某个方格的最大序号,则若不删除,之后的任何火柴都不在该方格中,无法破坏该方格),即剪枝不拆除的情况;
单纯使用以上剪枝,仍然会超时,则考虑使用估计函数来进行深度剪枝:考虑当前剩余的所有完整方格中不相交的方格的个数K,则从当前状态开始,至少还需要拆除K个火柴,才可能达到没有完整方格的状态。因此 taken_num >= gMinTakenNum
改为 taken_num + SeperateCompleteSquareNum() > gMinTakenNum
,进行剪枝。
实现方法
可以采用单纯的剪枝,或者采用IDA算法。
实现(c++)
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<vector> #include<algorithm> #define INFINITE 1 << 30 #define MAX_MATCH_NUM 2*5*6 #define MAX_SQUARE_NUM MAX_MATCH_NUM*5 using namespace std; bool gMatchInSquare[MAX_MATCH_NUM][MAX_SQUARE_NUM]; //判断火柴i是否位于方块j中 bool gSquareComplete[MAX_SQUARE_NUM]; //方块s是否完整 int gMaxMatchInSquare[MAX_SQUARE_NUM]; //方块s中最大的火柴序号 int gMinTakenNum; //最少需要拿走的火柴数目 int gTotalSquareNum; //没有任何火柴被拿走的情况下,总的方格数目 int gTotalMatchNum; //没有任何火柴被拿走的情况下,总的火柴数 vector<int> gNotMissedMatch; //没有被拿走的火柴集合,从中选择拿走的火柴 //初始化,主要是对于S*S的网格,判断 每个火柴位于那些方格中,以及每个方格中的最大的火柴序号 void Init(int size){ memset(gMatchInSquare, false, sizeof(gMatchInSquare)); memset(gSquareComplete, true, sizeof(gSquareComplete)); gTotalMatchNum = 2 * (size + 1)*size; int s = size; gTotalSquareNum = 0; while (s > 0){ gTotalSquareNum += s*s; s--; } s = 1; int total_square_index = 0; while (s <= size){ for (int square_index = 0; square_index < (size - s + 1)*(size - s + 1); square_index++){ int match_index = (square_index / (size - s + 1))*(2 * size + 1) + (square_index % (size - s + 1)); int up_beg = match_index; int left_beg = match_index + size; int right_beg = left_beg + s; int down_beg = up_beg + s*(1 + size*2); for (int i = 0; i < s; i++){ gMatchInSquare[up_beg + i][total_square_index] = true; gMatchInSquare[down_beg + i][total_square_index] = true; gMatchInSquare[left_beg + i*(2 * size + 1)][total_square_index] = true; gMatchInSquare[right_beg + i*(2 * size + 1)][total_square_index] = true; } gMaxMatchInSquare[total_square_index] = down_beg + s - 1; total_square_index++; } s++; } } //判断火柴m位于那些完整的方格中,以及m是否是某些网格的最大序号火柴 void MatchInCompleteSquare(int m, vector<int>& complete_square_contain_match, bool* match_is_max){ *match_is_max = false; for (int s = 0; s < gTotalSquareNum; s++){ if (gMatchInSquare[m][s] && gSquareComplete[s]){ complete_square_contain_match.push_back(s); if (gMaxMatchInSquare[s] == m){ *match_is_max = true; } } } } //获得当前剩余的完整网格中,不相交的网格的数目 int SeperateCompleteSquareNum(int n){ int result = 0; typedef pair<int, int> MatchNumSquarePair; vector<MatchNumSquarePair> ms_vec; for (int s = 0; s < gTotalSquareNum; s++){ if (!gSquareComplete[s]) continue; int num = 0; for (int m = 0; m < gTotalMatchNum; m++){ if (gMatchInSquare[m][s]) num++; } ms_vec.push_back(MatchNumSquarePair(num, s)); } sort(ms_vec.begin(), ms_vec.end()); vector<bool> match_used(gTotalMatchNum, false); for (int i = 0; i < ms_vec.size(); i++){ MatchNumSquarePair ms_pair = ms_vec[i]; bool ok = true; for (int m = n; m < gTotalMatchNum; m++){ if (match_used[m] && gMatchInSquare[m][ms_pair.second]){ ok = false; } } if (ok){ for (int m = n; m < gTotalMatchNum; m++){ if (gMatchInSquare[m][ms_pair.second]){ match_used[m] = true; } } result++; } } return result; } /* //单纯的估计函数进行剪枝,不适用IDA算法 void Destroy(int n, int taken_num, int left_complete_square){ if (n == gNotMissedMatch.size()){ return; } if (left_complete_square == 0){ gMinTakenNum = gMinTakenNum < taken_num ? gMinTakenNum : taken_num; return; } //估价函数剪枝 if (taken_num + SeperateCompleteSquareNum(gNotMissedMatch[n]) >= gMinTakenNum){ return; } int match = gNotMissedMatch[n]; vector<int> complete_square_contain_match; bool match_is_max_in_square; MatchInCompleteSquare(match, complete_square_contain_match, &match_is_max_in_square); //如果火柴 match_index 不存在任何一个剩余的完整的方块中,则不必拆除match_index,剪枝1 if (complete_square_contain_match.empty()){ Destroy(n + 1, taken_num, left_complete_square); } else{ //如果火柴 match_index 是当前剩余的某个完整方块的构成火柴的最大的序号,则必须进行拆除,即剪枝不拆除的情况;剪枝2 if (!match_is_max_in_square){ Destroy(n + 1, taken_num, left_complete_square); } for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = false; } Destroy(n + 1, taken_num + 1, left_complete_square - complete_square_contain_match.size()); for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = true; } } }*/ /* //IDA 迭代加深,每次只增加1个深度 void Destroy(int n, int taken_num, int left_complete_square, bool* destroy_over){ if (*destroy_over) return; if (n == gNotMissedMatch.size()){ return; } if (left_complete_square == 0){ *destroy_over = true; return; } int seperate_complete_square_num = SeperateCompleteSquareNum(gNotMissedMatch[n]); if (taken_num + seperate_complete_square_num > gMinTakenNum){ return; } int match = gNotMissedMatch[n]; vector<int> complete_square_contain_match; bool match_is_max_in_square; MatchInCompleteSquare(match, complete_square_contain_match, &match_is_max_in_square); if (complete_square_contain_match.empty()){ Destroy(n + 1, taken_num, left_complete_square, destroy_over); } else{ if (!match_is_max_in_square){ Destroy(n + 1, taken_num, left_complete_square, destroy_over); } for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = false; } Destroy(n + 1, taken_num + 1, left_complete_square - complete_square_contain_match.size(), destroy_over); for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = true; } } } */ //IDA迭代加深,每次可能增加多个深度,由next_min_taken_num指定 void Destroy(int n, int taken_num, int left_complete_square, int & next_min_taken_num){ if (next_min_taken_num <= gMinTakenNum){ return; } if (n == gNotMissedMatch.size()){ return; } if (left_complete_square == 0){ next_min_taken_num = next_min_taken_num < taken_num ? next_min_taken_num : taken_num; return; } int seperate_complete_square_num = SeperateCompleteSquareNum(gNotMissedMatch[n]); if (taken_num + seperate_complete_square_num > gMinTakenNum){ next_min_taken_num = next_min_taken_num < taken_num + seperate_complete_square_num ? next_min_taken_num : seperate_complete_square_num + taken_num; return; } int match = gNotMissedMatch[n]; vector<int> complete_square_contain_match; bool match_is_max_in_square; MatchInCompleteSquare(match, complete_square_contain_match, &match_is_max_in_square); if (complete_square_contain_match.empty()){ Destroy(n + 1, taken_num, left_complete_square, next_min_taken_num); } else{ if (!match_is_max_in_square){ Destroy(n + 1, taken_num, left_complete_square, next_min_taken_num); } for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = false; } Destroy(n + 1, taken_num + 1, left_complete_square - complete_square_contain_match.size(), next_min_taken_num); for (int i = 0; i < complete_square_contain_match.size(); i++){ int s = complete_square_contain_match[i]; gSquareComplete[s] = true; } } } //IDA方法 void Resolve(int left_complete_square){ gMinTakenNum = SeperateCompleteSquareNum(gNotMissedMatch[0]); int next_min_taken_num; bool destroy_over; while (true){ //IDA2 next_min_taken_num = INFINITE; Destroy(0, 0, left_complete_square, next_min_taken_num); if (next_min_taken_num <= gMinTakenNum){ gMinTakenNum = next_min_taken_num; return; } gMinTakenNum = next_min_taken_num; /* IDA1 destroy_over = false; Destroy(0, 0, left_complete_square, &destroy_over); if (destroy_over){ return; } gMinTakenNum++; */ } } int main(){ int T; scanf("%d", &T); while (T--){ int size, k; scanf("%d %d", &size, &k); Init(size); gNotMissedMatch.clear(); for (int i = 0; i < gTotalMatchNum; i++){ gNotMissedMatch.push_back(i); } gMinTakenNum = INFINITE; int missed_match_index, left_complete_square = gTotalSquareNum; for (int i = 0; i < k; i++){ scanf("%d", &missed_match_index); missed_match_index--; gNotMissedMatch.erase(find(gNotMissedMatch.begin(), gNotMissedMatch.end(), missed_match_index)); for (int j = 0; j < gTotalSquareNum; j++){ if (gMatchInSquare[missed_match_index][j] && gSquareComplete[j]){ gSquareComplete[j] = false; left_complete_square--; } } } //普通的 估价剪枝 //Destroy(0, 0, left_complete_square); //IDA 1或者2 Resolve(left_complete_square); printf("%d\n", gMinTakenNum); } return 0; }