[ABC345D] Tiling 的题解
题目大意
有一个由 行和 列组成的网格,每个单元格的边长为 ,我们有 块瓷砖。第 个图块 () 是一个大小为 的矩形。请判断是否有可能将这些图块放置在网格中,从而满足以下所有条件:
- 每个单元格都正好被一个图块覆盖。
- 有未使用的瓦片也没关系。
- 瓦片在放置时可以旋转或翻转。但是,每块瓦片必须与单元格的边缘对齐,不得超出网格。
其中 。
思路
显而易见的这个题目是一个 dfs 搜索题,毕竟 只有可怜的 。
对于每一块,可以选或者不选。如果不选择,那么就直接对下一块进行递归搜索。
如果选择,那么枚举这一块的方向和左上角的位置,判断这些区域是否没有被覆盖。
如果是空的,那么就将这里覆盖并且递归下一块。
但是不可避免的,这道题目还需要进行剪枝才可以通过。
剪枝
对于瓷砖排列的顺序,显然按面积从大到小排序是最优的。
因为在搜索时现将较大的放入后可以放置的区域就变少了,那么如果放置较小的瓷砖时就回更快的发现无解。
剪枝
在搜索的过程中,我们可以记录下一共覆盖的地板的数量。
如果将剩下来所有的瓷砖全部的体积加起来还是不能将他们放满的话,那么就直接返回。
AC Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
void io(){ios::sync_with_stdio(false);cin.tie(nullptr);}
template<typename T>void write(T x){if(x<0) cout<<'-', x=-x;if(x>9) write(x/10);cout<<(char)(x%10+48);}
const int N=20;
int num,n,m,s[N];
struct node{int x,y;}a[N];
bool cmp(node a,node b){return a.x*a.y>b.x*b.y;}
bool vis[N][N];
bool ck(){ //判断是否全部覆盖
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(!vis[i][j]) return 0;
return 1;
}
bool emp(int x,int xx,int y,int yy){ //判断从是否为空的
for(int i=x;i<xx;i++) for(int j=y;j<yy;j++) if(vis[i][j]) return 0;
return 1;
}
void fill(int x,int xx,int y,int yy,bool val){ //将这个区域覆盖
for(int i=x;i<xx;i++) for(int j=y;j<yy;j++) vis[i][j]=val;
}
void dfs(int k,int sum){
if(ck()) cout<<"Yes\n",exit(0);
if(k==num+1) return;
if(sum+s[k]<n*m) return; //剪枝 2
dfs(k+1,sum);
for(int i=1;i+a[k].x<=n+1;i++){ //竖着
for(int j=1;j+a[k].y<=m+1;j++){
if(emp(i,i+a[k].x,j,j+a[k].y)){
fill(i,i+a[k].x,j,j+a[k].y,1);
dfs(k+1,sum+a[k].x*a[k].y);
fill(i,i+a[k].x,j,j+a[k].y,0);
}
}
}
for(int i=1;i+a[k].y<=n+1;i++){ //横着
for(int j=1;j+a[k].x<=m+1;j++){
if(emp(i,i+a[k].y,j,j+a[k].x)){
fill(i,i+a[k].y,j,j+a[k].x,1);
dfs(k+1,sum+a[k].x*a[k].y);
fill(i,i+a[k].y,j,j+a[k].x,0);
}
}
}
}
void solve(){
cin>>num>>n>>m;
for(int i=1;i<=num;i++) cin>>a[i].x>>a[i].y;
sort(a+1,a+1+num,cmp); //剪枝 1
for(int i=num;i>=1;i--) s[i]=s[i+1]+a[i].x*a[i].y;
dfs(1,0);
cout<<"No\n";
}
signed main(){io();
int T=1;
while(T--){
solve();
}
return 0;
}