这里讨论一下小规模平铺类搜索的一种写法。
说明一下讨论的范围:
OK,先TOJ 3858
3858 . Home
Time Limit: 1.0 Seconds Memory Limit: 65536K
Total Runs: 187 Accepted Runs: 60Poorman rents a new house in Beijing recently. He has some furnitures and wants to move them into his new house.
In order to make the problem more clearly, the new house is 10 × 10 square. Every furniture can be represented by a rectangle which is a × b ( a > 0 , b > 0 ). Now given you the information about the furnitures and tell us whether poorman can move all his furnitures into his new house.
Input
There are several test cases. For each test case, the first line contains a single integer n ( 0 < n < 10 ) which is the number of poorman’s furnitures. Then there will be n following lines. For each line, there are two integers a, b ( a > 0 , b > 0 ) which means the furniture’s size is a × b.
Output
For each test case, if poorman can move all his furnitures into his house successfully, print “Yes” in a line. Otherwise print “No”.
Sample Input
3
5 10
4 5
5 6Sample Output
Yes
Source: TJU 2012 Team Selection
凭直觉,很容易想到,规定好放置顺序和方向的情况下,尽量靠上能更多的节省空间。
那这样的话,如果我能规定顺序和旋转方向,枚举放在哪里合适的时候,直接越往上越好,或者说,在左上角(1,1),向下x增大,向右y增大的坐标系里,我只需要枚举所有可能的y,x利用这个贪心性质,直接尽量往上贴了。
那这样的话,在考虑一种约定的放置顺序和方向的时候,我们只要设置一个数组(下面的代码中的lowest),记录当前状态下,每一列最下面的x坐标是多少,放置的时候,考虑放置所影响的y的区间,并做相应修改即可。(记得备份当前状态,因为y还是要dfs枚举的)
有人要问了,贪心往上贴,留下一些空腔,其他小方块能放得下怎么办?
答案是,不要在意,我们要枚举这些方块的放置顺序和旋转方向,这样,一种顺序是长的方块先放,导致上面留下很大空间被浪费,没关系,等一下一定枚举得到小块先放上去再放大块的情况的。
在下面的代码实现中,用next_permutation()枚举放置顺序,dfs()里枚举放置方向和左上角的y的位置,dfs里记得要以复制数组的形式备份当前状态,回退的时候要还原(因为放置上去以后还原没法直接正确推测的,和下面的题不一样)。
时间复杂度: O(n!×2n×n×Width2)
参数带进去感觉,好大的复杂度啊,但是实际在TOJ上跑,0.00秒,无语。
其实时间常数上还是有一定优化空间的,下面再讨论。
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
int n;
int piece[10][2];
int lowest[10];
int baklowest[10][10];
int perm[10];
int lowestinRange(int st,int en){
int ans=0;
for(int i=st;i<=en;i++)ans=max(ans,lowest[i]);
return ans;
}
void backupLowest(int depth){
for(int i=0;i<10;i++)baklowest[depth][i]=lowest[i];
}
void restoreLowest(int depth){
for(int i=0;i<10;i++)lowest[i]=baklowest[depth][i];
}
bool found;
void dfs(int depth){
if(depth==n){
found=true;
return;
}
int id=perm[depth];
for(int i=0;i<=10-piece[id][0];i++){
int mark=lowestinRange(i,i+piece[id][0]-1);
if(mark+piece[id][1]>10)continue;
backupLowest(depth);
for(int j=i;j<=i+piece[id][0]-1;j++)
lowest[j]=mark+piece[id][1];
dfs(depth+1);
if(found)return;
restoreLowest(depth);
}
for(int i=0;i<=10-piece[id][1];i++){
int mark=lowestinRange(i,i+piece[id][1]-1);
if(mark+piece[id][0]>10)continue;
backupLowest(depth);
for(int j=i;j<=i+piece[id][1]-1;j++)
lowest[j]=mark+piece[id][0];
dfs(depth+1);
if(found)return;
restoreLowest(depth);
}
}
int main(){
while(~scanf("%d",&n)){
for(int i=0;i<n;i++) scanf("%d%d",&piece[i][0],&piece[i][1]);
for(int i=0;i<n;i++)perm[i]=i;
found=false;
do{
memset(lowest,0,sizeof(lowest));
dfs(0);
}while(next_permutation(perm,perm+n)&&!found);
puts(found?"Yes":"No");
}
return 0;
}
继续,GCPC 2015(2015 German Collegiate Programming Contest) 的D题
Problem D: Carpets
The computer science Professor Toving Liles loves the floor tiles in his office so much that he wants to protect them from damage by careless students. Therefore, he would like to buy cheap small rectangular carpets from the supermarket and cover the floor such that:
1. The entire floor is covered.
2. The carpets do not overlap.
3. The carpets are rotated arbitrarily.
4. No carpet is cut into pieces.
But when checking the supermarket’s stock he begins to wonder whether he can accomplish his plan at all. Can you help him?
Input
The first line contains two integers W and H describing the size of his room (1≤W,H≤100) .
The second line contains an integer c, denoting the number of different carpet colors the supermarket has in stock (1≤c≤7) .
Each of the following c lines consists of three integers ai,wi , and hi , which means: thesupermarket’s stock contains ai carpets of size wi,hi and color i (1≤ai≤7;1≤wi≤100;1≤hi≤100) .
The supermarket has at most 7 carpets, i.e. ∑iai≤7 .
Output
For the given room dimensions and the supermarket’s stock of carpets, print “yes” if it is possible to cover the room with carpets as specified above and “no” otherwise.Sample Input 1
2 4
2
3 1 3
2 2 1Sample Output 1
yesSample Input 2
100 100
3
4 42 42
1 100 16
1 32 42Sample Output 2
no
和上面的比较类似,但是有区别:这里的目标是,任选一部分,判断能否把整个区域平铺满。地毯数量减少了,区域增大了不少。
既然要考虑的是放满,同样可以选择,通过枚举来约定放置顺序和方向,在约定的基础上,枚举y,放置策略仍然是尽量往上放的贪心,不同的是,放置之前要考虑是否会产生一个空腔,如果会产生,那就不应该放上去。
看上去很不错,可是交上去就TLE了。
有一个贪心的性质是这题新增且需要利用的:你一定要把最上面有缺的那一行用方块填满,如果手上剩下的方块不能填满,那前面的放置方法就不正确。而且,如果最高的有空缺的那一行,有空缺的y有多个,你肯定要把最左的y能找个方块恰当的放上去,不然那个空没办法填满,铺满也就无从谈起。
所以这里在写的时候,对当前要继续考虑的状态,先找到放置方块总高度最小且最左的y,然后,只用对着那个y,把那个y当做必须要放好的左上角位置,枚举剩下的方块放上去,直到铺满,或者试完以后还是没法铺满为止。
时间复杂度: O(n!×2n×n×Width)
其中 n!×2n 这么多放置的顺序和方向很多时候不会被枚举完了,而会在dfs的过程中自然的被剪枝了。
而且, n!×2n 的放置的顺序和方向的枚举给dfs还有个好处,不会像上面的next_permutation写法一样,把排列中没改变位置顺序的方块给重复放置,变相减小了常数。
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int piece[15][2];
bool taken[15];
int height[105];
int pcnt=0;
int totw,toth;
bool placeAble(int stw,int len,int h){
if(height[stw]+h>toth)return false;
if(stw+len>totw)return false;
for(int i=stw;i<stw+len-1;i++){
if(height[i]!=height[i+1]){
return false;
}
}
return true;
}
bool fullfill(){
for(int i=0;i<totw;i++){
if(height[i]!=toth)return false;
}
return true;
}
void place(int stw,int len,int h){
for(int i=stw;i<stw+len;i++){
height[i]+=h;
}
}
bool dfs(int depth){
if(fullfill())return true;
if(depth>=pcnt)return false;
int minh=205;
for(int j=0;j<totw;j++)minh=min(minh,height[j]);
for(int j=0;j<totw;j++){
if(minh==height[j]){
for(int i=0;i<pcnt;i++){
if(!taken[i]){
if(placeAble(j,piece[i][0],piece[i][1])){
place(j,piece[i][0],piece[i][1]);
taken[i]=true;
if(dfs(depth+1))return true;
taken[i]=false;
place(j,piece[i][0],-piece[i][1]);
}
if(placeAble(j,piece[i][1],piece[i][0])){
place(j,piece[i][1],piece[i][0]);
taken[i]=true;
if(dfs(depth+1))return true;
taken[i]=false;
place(j,piece[i][1],-piece[i][0]);
}
}
}
break;
}
}
return false;
}
int main(){
int c;
scanf("%d%d%d",&totw,&toth,&c);
for(;c--;){
int a,wi,hi;
scanf("%d%d%d",&a,&wi,&hi);
for(;a--;){
piece[pcnt][0]=wi;
piece[pcnt++][1]=hi;
}
}
puts(dfs(0)?"yes":"no");
return 0;
}