[hihocoder1048]状态压缩2

问题描述

具体介绍请参考 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;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值