poj_1084 剪枝-IDA*

题目大意

给出一个由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;
}
时间: 2024-10-28 20:50:24

poj_1084 剪枝-IDA*的相关文章

埃及分数 IDA*

description 对于每一个非负有理数,我们知道它一定能划归成某些特殊真分数之和,特殊真分数要满足它们的分子为1,但是我们知道,对于无穷级数1/2+1/3+1/4….虽然,它是发散的,但是改级数增长得极为缓慢,例如到了数百万之后,和也在18~19左右. 若干年来,不断有人宣称发现了该级数的特殊性质,这些都对这个问题的研究起到了深远的影响. 你的任务来了,要求给你个真分数,你需要将其化简为最少的若干特殊真分数之和,你要输出这个序列(序列按递增序). 如果有不同的方案,则分数个数相同的情况下使

[BZOJ 1085][SCOI 2005]骑士精神(IDA*搜索)

题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1085 考虑到深度不超过15,IDA*搜索可做. 估价函数h()=当前不在目标位置的棋子个数. 然后其他细节就和普通的迭代加深一样了. #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm>

UVa 11212 编辑书稿(dfs+IDA*)

https://vjudge.net/problem/UVA-11212 题意:给出n个自然段组成的文章,将他们排列成1,2...,n.每次只能剪切一段连续的自然段,粘贴时按照顺序粘贴. 思路:状态空间的搜索问题. 首先介绍一下IDA*,它属于DFS,在DFS遍历的时候,设定一个深度上限maxd,当前结点n的深度为g(n),乐观估价函数为h(n),则当g(n)+h(n)>maxd时应           该剪枝.这样的算法就是IDA*. 在这道题目中,由于最多就9个数,所以最多只需要剪切8次肯定

UVA-1343 The Rotation Game (IDA*)

题目大意:数字1,2,3都有八个,求出最少的旋转次数使得图形中间八个数相同.旋转规则:对于每一长行或每一长列,每次旋转就是将数据向头的位置移动一位,头上的数放置到尾部.若次数相同,则找出字典序最小旋转次序. 题目分析:IDA*,若当前在第cur层,中间八个数中1,2,3的个数分别为a,b,c.则d=8-max(a,b,c)便是达到目标还需要的理想次数,若cur+d>maxd,则剪枝.<入门经典>上提供了一种BFS的思路,分别以1,2,3为目标广搜3次,不过自己的码力还是太弱,并没有用那种

POJ - 2286 - The Rotation Game (IDA*)

IDA*算法,即迭代加深的A*算法,实际上就是迭代加深+DFS+估价函数 题目传送:The Rotation Game AC代码: #include <map> #include <set> #include <list> #include <cmath> #include <deque> #include <queue> #include <stack> #include <bitset> #include

[poj2286]The Rotation Game (IDA*)

//第一次在新博客里发文章好紧张怎么办 //MD巨神早已在一个小时前做完了 The Rotation Game Time Limit: 15000MS Memory Limit: 150000K Total Submissions: 5950 Accepted: 1992 Description The rotation game uses a # shaped board, which can hold 24 pieces of square blocks (see Fig.1). The b

uva 11212 - Editing a Book(迭代加深搜索 IDA*) 迭代加深搜索

迭代加深搜索 自己看的时候第一遍更本就看不懂..是很水,但智商捉急也是没有办法的事情. 好在有几个同学已经是做过了这道题并且对迭代加深搜索的思路有了一定的了解,所以在某些不理解的地方询问了一下他们的见解, 真的是很有帮助,也许自己想要想很久才能想明白,还会很痛苦,稍微问一下别人的想法,点上一个方向,剩下的自己就能想得明白了. 迭代加深. 把answer(需要的步数或其他)在主函数里面从零往上递加,此之谓 "层数",亦可谓之"深度".用书上的话就是: 从小到大枚举深度

【启发式搜索】A*与IDA*学习笔记

搞了这么久发现自己到现在还不会启发式搜索ヾ(?`Д′?)所以今天正好趁着搜索练习题的风去搞了启发式搜索 A*搜索算法,俗称A星算法.这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法.常用于游戏中的NPC的移动计算,或在线游戏的BOT的移动计算上. 该算法像Dijkstra算法一样,可以找到一条最短路径:也像BFS一样,进行启发式的搜索. 在此算法中,如果以 g(n)表示从起点到任意顶点n的实际距离,h(n)表示任意顶点n到目标顶点的估算距离,那么 A*算法的公式为:f(n)=g(n

HDU3459:Rubik 2&amp;#215;2&amp;#215;2(IDA)

Problem Description Sonny is probably the only computer science Ph.D. student who cannot solve a Rubik's cube. One day, he came across a neat little 2×2×2 Rubik's cube, and thought, "Finally, here's a cube that's easy enough for me to do!" Nope,