P10975 Mondriaan's Dream 解题报告
题目传送门
题目大意
给定一个
数据范围:
思路:
考虑怎么放才能刚好填满网格。
可以想到,如果先放横着的,再放竖着的,那么当我们将横着的都放完后,若竖着的恰好能刚好嵌进去,说明这是一个合法方案。
也就是说,放完横着的矩形后放竖着的矩形的方法的唯一确定的,那么:
求总的方案数其实就是求横着放且合法(使竖着的能嵌进去刚好铺满网格)的方案数。
因为放横着的矩形时拓展的方向是横向的,就是说我们放矩形时,当前放的这个矩形只影响到下一列,这启示我们将“列号”作为 dp 的阶段,同时由于上一列的放置情况会影响到当前这一列,所以我们需要将上一列伸出来的部分作为状态中的一维才能转移。
那么如何表示上一列那些地方伸出来了呢?
如果用 bool 数组来表示第
设
其中
,即第 列伸到第 的小方格和 列放置的小方格不重复;- 每一列,所有连续着空着的小方格必须是偶数个,因为竖着的矩形必须要能嵌入。
初始化:
按定义这里是:前第
首先,这里没有
其次,没有伸出来,即没有横着摆的。即这里第
目标:
即整个棋盘处理完的方案数。
再用集合划分的思想来解释一下。
首先要 “化零为整”,即用一个集合表示一类情况,对于这道题,假设我们放矩形时是对于每列都从上往下放,那么可以根据当前放到了第几列来划分集合。
但这样划分我们无法找到各个集合之间的转移关系,所以还要再划分一次。
集合划分的依据就是寻找集合中元素的不同点,发现在摆完前
所以状态表示为:
接着就是 “化整为零” 的过程,即状态计算,同上。
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 12, M = 1 << N;
typedef long long ll;
int n, m;
vector<int> tran[M];
ll dp[N][M];
bool st[M];
bool check(int x) { //判断该状态是否满足所有连续着空着的小方格必须是偶数个
int cnt = 0;
for(int i = 0; i < n; i++) {
if(x >> i & 1) {
if(cnt & 1) return false;
cnt = 0;
}
else ++cnt;
}
if(cnt & 1) return false;
return true;
}
int main() {
while(scanf("%d%d", &n, &m), n || m) {
for(int i = 0; i < 1 << n; i++) st[i] = check(i); //提前预处理那哪些状态是合法的
for(int i = 0; i < 1 << n; i++) {
tran[i].clear(); //多测清空
for(int j = 0; j < 1 << n; j++)
if(!(i & j) && st[i | j])
tran[i].push_back(j); //提前预处理出每个可行状态能由那些状态转移过来
}
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for(int i = 1; i <= m; i++)
for(int j = 0; j < 1 << n; j++)
for(int k = 0; k < tran[j].size(); k++)
dp[i][j] += dp[i - 1][tran[j][k]];
printf("%lld\n", dp[m][0]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!