【题解】蒙德里安的梦想

前言

很经典的状态压缩dp,第一次提交超时,不知道改了改什么地方,再提交居然A了,很离谱…

题目: 蒙德里安的梦想
题意:

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:

方案示例

输入格式

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式

每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤11

输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
思路:
  • 我们会发现,对于任意一种情况,只要它的横向方块确定了,那么竖向的方块只有一种可能,所以我们只需要枚举横向方块的情况就可以。总方案数:等于只放横向方块的合法方案数。
  • 怎么判断是否合法?其实就是看剩余的位置是否能用竖向方块填满,如果可以就说明是合法的。可以每一列来看,如果有连续的奇数个方格没有放,那么说明不合法。
  • 另外在dp时需要保证上一列和下一列不能存在 “冲突” ,也即不能让某列的一个格子被两个方块都放过。
  • 用M来表示每列最多有多少种情况,M是对应的二进制数。因为我们用二进制的形式表示某种情况,所以M的取值范围是[000…0, 111…1]。
  • 这里的某个 “情况” 指的是从上一列凸出来的方块摆放 “情况” 。
  • 因为我们要额外向后询问一列,所以N要设置为12。
代码:
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 12, M = 1 << N;
#define ll long long 

ll f[N][M]; //对于f[i][(10010)B]表示第i列的第一行和第四行有方块凸出来,存的是这种情况下的方案数。
bool st[M];//记录第i种情况是否符合条件: 也即不存在连续奇数个零
int n, m;

int main()
{
	while (cin >> n >> m, n || m)
	{
		memset(f, 0, sizeof f);

		//对于每一列进行预处理,遍历所有情况将含有奇数个连续零的可能剔除。
		for (int i = 0; i < 1 << n; ++i)
		{
			st[i] = true;
			int cnt = 0;//记录连续零的个数
			for (int j = 0; j < n; ++j)
			{
				//注意是询问i的二进制数中有多少个0,所以应该是右移
				if (i >> j & 1)
				{
					if (cnt & 1)st[i] = false;
					cnt = 0;
				}
				else cnt++;
			}
			//如果最后没有遇到零那么不会在循环里判断,所以要特判
			if (cnt & 1)st[i] = false;
		}

		//因为第1列不存在前面的列,所以不可能有方块凸出来,于是只有(000...0)B这一种情况
		f[0][0] = 1;
		//遍历所有列
		for (int i = 1; i <= m; ++i)
		{
			for (int j = 0; j < 1 << n; ++j)
			{
				for (int k = 0; k < 1 << n; ++k)
				{
					//st[j | k]表示的是两种情况合并后的情况
					//注意,比较运算符的优先级高于按位运算符
					if ((j & k) == 0 && st[j | k])
					{
						f[i][j] += f[i - 1][k];
					}
				}
			}
		}
		//我们是从0开始枚举的,所以只需要枚举到m-1行合法的情况即可。那么对应的就是第m行没有任何方块凸出来。
		cout << f[m][0] << endl;
	}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值