问题描述
具体介绍请参考
hihocoder的官网。
算法&部分代码
这里标记中使用的上下左右指的是同一蛋糕的另外一个格子相对于本格子的位置,例如左上角的格子只可能有DOWN和RIGHT两个方向。
1. 深搜+剪枝
从上到下,从左到右,枚举格子的状态,深搜到底则+1。剪枝是基于祖先节点的情况不存在,则本节点的情况也不存在。
深搜是利用迭代算法实现的。简单证明可以得到O(4^n^m)的复杂度,剪枝后的复杂度未思考,但是测试中当n>20就有1s以上的延时,提交后也超时。
enum {
NONE,
UP,
DOWN,
LEFT,
RIGHT
};
bool incre(int &i,int &j,int n,int m){
if (i == n-1 && j == m-1)
return false;
if (++j == m){
++i;
j = 0;
}
return true;
}
void decre(int &i,int &j,int n,int m){
if (j == 0){
--i;
j = m-1;
}
else
--j;
}
int times(int n,int m){
int status[1000][5] = {0};
int i = 0,j = 0,result = 0;
bool forw = true;
while (i > -1 && j > -1){
if (status[i][j] == NONE){
if (j < m-1 && status[i][j+1] == NONE)
status[i][j] = RIGHT;
else if (i < n-1 && status[i+1][j] == NONE)
status[i][j] = DOWN;
}
else if (status[i][j] == RIGHT) {
status[i][j+1] = NONE;
if (i < n-1 && status[i+1][j] == NONE)
status[i][j] = DOWN;
else
status[i][j] = NONE;
}
else if (status[i][j] == DOWN){
status[i][j] = NONE;
status[i+1][j] = NONE;
}
if (status[i][j] == NONE){
decre(i,j,n,m);
forw = false;
}
else {
if (status[i][j] == RIGHT){
status[i][j+1] = LEFT;
incre(i,j,n,m);
forw = true;
}
else if (status[i][j] == DOWN){
status[i+1][j] = UP;
incre(i,j,n,m);
forw = true;
}
else {
if (forw){
if (!incre(i,j,n,m)){
if (++result == 1000000007)
result = 0;
decre(i,j,n,m);
forw = false;
}
}
else
decre(i,j,n,m);
}
}
}
return result;
}
2. 未剪枝的动态规划
逐行枚举,每行有4^m个状态,表示当前行每个格子的4种状态的组合下,对应可以有多少种可能摆放。
进行过程中,每行对于该行以及前一行共两行格子的状态(共8^m个)逐一枚举,如果状态成立,则累加,否则不累加。
可以逐行枚举进行动态规划的原因是每一行的状态成立与否,只受上一行影响,且只影响下一行。
未剪枝的动态规划的时间复杂度是O(n*8^m),这里存在一个m的指数项,但由于m最大为5,则2^15~32000,大致等于常数项。
因为m的数目不定,而且每个格子只对应于4个状态,不适合用整形数组存储状态组合。这里就利用了
状态压缩,用一个数字存储状态。
其中由右到左第2i与第2i+1个数字位表示该行从左到右第i个格子的状态,00表示UP,01表示DOWN,10表示LEFT,11表示RIGHT。(都是从0开始数)
int top[1<<10];
int top2[1<<10];
bool flags[1<<20];
void init_flags(int m){
int len2 = 1<<(2*2*m),tmp_status;
bool flag;
for (int j = 0;j < len2;++j){
flag = true;
for (int k = 0;k < m && flag;++k){
tmp_status = (j >> (2*k)) & 3;
if (tmp_status == 0 && ((3 & (j>>(2*(k+m)))) != 1))
flag = false;
else if (tmp_status == 2 && (k == 0 || ((3 & (j>>(2*(k-1)))) != 3)))
flag = false;
else if (tmp_status == 3 && (k == m-1 || ((3 & (j>>(2*(k+1)))) != 2)))
flag = false;
else if (tmp_status != 0 && ((3 & (j>>(2*(k+m)))) == 1))
flag = false;
else if ((tmp_status != 2 && (k > 0 && ((3 & (j>>(2*(k-1)))) == 3))))
flag = false;
else if (tmp_status != 3 && (k < m-1 && ((3 & (j>>(2*(k+1)))) == 2)))
flag = false;
}
flags[j] = flag;
}
}
//0:UP 1:DOWN 2:LEFT 3:RIGHT
int times2(int n,int m){
int len = 1 << (2*m),len2 = len << (2*m);
bool flag;
init_flags(m);
if (m == 3)
top[21] = top[27] = top[45] = 1;
else if (m == 4)
top[85] = top[91] = top[109] = top[181] = top[187] = 1;
else if (m == 5)
top[341] = top[347] = top[365] = top[437] =
top[443] = top[725] = top[731] = top[749] = 1;
int right_map=(1<<(2*m))-1;
for (int i = 1;i < n;++i){
for (j = 0;j < len2;++j){
flag = flags[j];
if (i == n-1) {
for (int k = 0;k < m && flag;++k){
if (((j >> (2*k)) & 3) == 1)
flag = false;
}
}
if (flag)
top2[j & right_map] = (top[j >> (2*m)] + top2[j & right_map]) % MOD;
}
for (int i = 0;i < len;++i){
top[i] = top2[i];
top2[i] = 0;
}
}
int result = 0;
for (int i = 0;i < len;++i)
result = (result + top[i]) % MOD;
return result;
}
3. 剪枝过的动态规划
注意到主要循环体内每次都需要检测状态是否成立(flags[j]是否为true),换做提前列出所有flags[j]等于true的j,然后枚举这些j,减少循环的次数。
这部分代码大部分和上方相似,这里仅列出剪枝相关代码。
int true_list[1<<20];
init_flags(m);
int true_len = 0;
for (int i = 0;i < len2;++i)
if (flags[i])
true_list[true_len++] = i;
for (int i = 1;i < n;++i){
for (int h = 0;h < true_len;++h){
j = true_list[h];
4. 主函数
int main()
{
int n,m;
cin >> n >> m;
if ((n*m) & 1)
cout << 0 << endl;
else
cout << times2(n,m) << endl;
return 0;
}
5. 测试代码
包括print全局变量,以及用深搜做真值的寻找错误样例的代码。
void print_top(int m){
int len = 1<<(2*m);
for (int i = 0;i < len;++i)
if (top[i] > 0)
cout << i << ':' << top[i] << ' ';
cout << endl;
}
void print_flags(int m){
int len = (1<<(4*m));
cout << "flags: ";
for (int i = 0;i < len;++i)
if (flags[i])
cout << i << ',';
cout << endl;
}
int compare(){
int time1,time2;
for (int i = 2;i < 6;++i){
for (int j = 3;j < 6;++j) {
for (int i = 0;i < (1<<10);++i)
top[i] = top2[i] = 0;
cout << i << ' ' << j << ':';
time1 = times(i,j);
time2 = times2(i,j);
cout << ' ' << time1 << ' ' << time2 << ' ';
if (time1 == time2)
cout << "True" << endl;
else
cout << "False" << endl;
}
}}
全部代码
#include <iostream>
using namespace std;
#define MOD 1000000007
enum {
NONE,
UP,
DOWN,
LEFT,
RIGHT
};
bool incre(int &i,int &j,int n,int m){
if (i == n-1 && j == m-1)
return false;
if (++j == m){
++i;
j = 0;
}
return true;
}
void decre(int &i,int &j,int n,int m){
if (j == 0){
--i;
j = m-1;
}
else
--j;
}
int times(int n,int m){
int status[1000][5] = {0};
int i = 0,j = 0,result = 0;
bool forw = true;
while (i > -1 && j > -1){
if (status[i][j] == NONE){
if (j < m-1 && status[i][j+1] == NONE)
status[i][j] = RIGHT;
else if (i < n-1 && status[i+1][j] == NONE)
status[i][j] = DOWN;
}
else if (status[i][j] == RIGHT) {
status[i][j+1] = NONE;
if (i < n-1 && status[i+1][j] == NONE)
status[i][j] = DOWN;
else
status[i][j] = NONE;
}
else if (status[i][j] == DOWN){
status[i][j] = NONE;
status[i+1][j] = NONE;
}
if (status[i][j] == NONE){
decre(i,j,n,m);
forw = false;
}
else {
if (status[i][j] == RIGHT){
status[i][j+1] = LEFT;
incre(i,j,n,m);
forw = true;
}
else if (status[i][j] == DOWN){
status[i+1][j] = UP;
incre(i,j,n,m);
forw = true;
}
else {
if (forw){
if (!incre(i,j,n,m)){
if (++result == 1000000007)
result = 0;
decre(i,j,n,m);
forw = false;
}
}
else
decre(i,j,n,m);
}
}
}
return result;
}
int top[1<<10];
int top2[1<<10];
bool flags[1<<20];
int true_list[1<<20];
void init_flags(int m){
int len2 = 1<<(2*2*m),tmp_status;
bool flag;
for (int j = 0;j < len2;++j){
flag = true;
for (int k = 0;k < m && flag;++k){
tmp_status = (j >> (2*k)) & 3;
if (tmp_status == 0 && ((3 & (j>>(2*(k+m)))) != 1))
flag = false;
else if (tmp_status == 2 && (k == 0 || ((3 & (j>>(2*(k-1)))) != 3)))
flag = false;
else if (tmp_status == 3 && (k == m-1 || ((3 & (j>>(2*(k+1)))) != 2)))
flag = false;
else if (tmp_status != 0 && ((3 & (j>>(2*(k+m)))) == 1))
flag = false;
else if ((tmp_status != 2 && (k > 0 && ((3 & (j>>(2*(k-1)))) == 3))))
flag = false;
else if (tmp_status != 3 && (k < m-1 && ((3 & (j>>(2*(k+1)))) == 2)))
flag = false;
}
flags[j] = flag;
}
}
void print_top(int m){
int len = 1<<(2*m);
for (int i = 0;i < len;++i)
if (top[i] > 0)
cout << i << ':' << top[i] << ' ';
cout << endl;
}
void print_flags(int m){
int len = (1<<(4*m));
cout << "flags: ";
for (int i = 0;i < len;++i)
if (flags[i])
cout << i << ',';
cout << endl;
}
//0:UP 1:DOWN 2:LEFT 3:RIGHT
int times2(int n,int m){
int len = 1 << (2*m),len2 = len << (2*m);
bool flag;
init_flags(m);
int true_len = 0;
for (int i = 0;i < len2;++i)
if (flags[i])
true_list[true_len++] = i;
if (m == 3)
top[21] = top[27] = top[45] = 1;
else if (m == 4)
top[85] = top[91] = top[109] = top[181] = top[187] = 1;
else if (m == 5)
top[341] = top[347] = top[365] = top[437] =
top[443] = top[725] = top[731] = top[749] = 1;
int right_map=(1<<(2*m))-1,j;
for (int i = 1;i < n;++i){
for (int h = 0;h < true_len;++h){
j = true_list[h];
flag = true;
if (i == n-1) {
for (int k = 0;k < m && flag;++k){
if (((j >> (2*k)) & 3) == 1)
flag = false;
}
}
if (flag)
top2[j & right_map] = (top[j >> (2*m)] + top2[j & right_map]) % MOD;
}
for (int i = 0;i < len;++i){
top[i] = top2[i];
top2[i] = 0;
}
}
int result = 0;
for (int i = 0;i < len;++i)
result = (result + top[i]) % MOD;
return result;
}
int main()
{
int n,m;
cin >> n >> m;
if ((n*m) & 1)
cout << 0 << endl;
else
cout << times2(n,m) << endl;
// int time1,time2;
// for (int i = 2;i < 6;++i){
// for (int j = 3;j < 6;++j) {
// for (int i = 0;i < (1<<10);++i)
// top[i] = top2[i] = 0;
// cout << i << ' ' << j << ':';
// time1 = times(i,j);
// time2 = times2(i,j);
// cout << ' ' << time1 << ' ' << time2 << ' ';
// if (time1 == time2)
// cout << "True" << endl;
// else
// cout << "False" << endl;
// }
// }
return 0;
}