100道动态规划——31 POJ 2411 && POJ 2663 && POJ 3420 状态压缩 矩阵快速幂

        首先感谢原作者http://blog.csdn.net/shiwei408/article/details/8821853,这篇blog写的确实挺不错的

        切入正题:

        这三道问题具有相似性,于是我从简单的开始说起,POJ 2411

        考虑大小为n×m的矩形

        定义状态dp[i][s]代表当前处于第i行且第i行状态为s时的方案总数,每一位上用0表示没有放置方块,1表示有。因此初始条件是dp[0][(1<<m)-1]=1,也就是说第0行已经放满了,从第1行开始放起,实际上也就是打消了在第1行竖着放的念头。

        状态转移方程是dp[i][s]=∑(ss∈K(s))dp[i-1][ss],其中K(s)是一个以01串为定义域,以01串的集合为值域的函数。K(s)={ss|第i行的状态为s时,第i-1行可以为ss}

        举个例子,考虑大小为4×4的矩形,现在考虑第2行,状态为1111时,依据状态转移方程,上一行的状态ss可以是1111(上一行全都是满的,意味着这一行都是横着放),0000( 上一行都是空的,意味着这一行全都是竖着放的),0011(这一行前面两个竖着放,后一个横着放)......

        因此需要一个dfs的预处理,把所有从上一行可以到下一行的状态预处理出来,然后就直接走for循环了

        dfs预处理的方法是考虑3个量,第一个量是s,第二个量是now,第三个量是pre,s当前考虑第s列,now表示当前行的状态,pre表示上一行的状态

        假若是把一块骨牌横着放的话,now就变成了now<<2|3,同时要求上一行也是两个1,因此pre同时也变成了pre<<2|3,s变成了s+2

        考虑把骨牌竖着放的话,now变成了额now<<1|1,同时要求上一行的这一块是不放骨牌的,因此pre变成了pre<<1,s变成了s+1

        考虑不放骨牌的话,now就是now<<1,但是上一行的这一块必须要有骨牌,不然以后再也没有机会填满了,因此pre变成了pre<<1|1,s变成了s+1

这一题的代码:因为POJ不支持C++11的缘故,把自己写的代码等价替换掉了。之所以有一个交换n和m的步骤是为了加速

实际上最快的方法是打表。。。。

#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
const int maxm=1<<11;
typedef long long ll;
typedef pair<int,int> pa;
//using ll=long long;
//using pa=pair<int,int>;

int n,m,t;
vector<pa> state;
ll dp[13][maxm+10];
void dfs(int s,int now,int pre);

int main(){
    ios_base::sync_with_stdio(false);

    while(cin>>n>>m&&n+m){
        if((n&1)&&(m&1))
            cout<<0<<endl;
        else{
            if(n<m)
                swap(n,m);
            state.clear();
            dfs(0,0,0);
            memset(dp,0,sizeof dp);
            dp[0][(1<<m)-1]=1;

            for(int i=0;i<n;++i)
            for(int j=0;j<state.size();++j)
            //for(const auto& x:state)
                dp[i+1][state[j].second]+=dp[i][state[j].first];

            cout<<dp[n][(1<<m)-1]<<endl;
        }
    }
    return 0;
}

void dfs(int s, int now, int pre){
    if(s<=m){
        if(s==m)
            state.push_back(make_pair(pre,now));
        else{
            dfs(s+2,now<<2|3,pre<<2|3);
            dfs(s+1,now<<1|1,pre<<1);
            dfs(s+1,now<<1,pre<<1|1);
        }
    }
}


        接下来讲POJ 2663

        实际上和刚刚这题几乎一样,把列锁定为3就好,先预处理出上一行到下一行的所有状态,然后和上面一样的run就好,只不过这个可以把dfs放在main的开头,因为列数是固定的,最快的办法也是打表。。。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;
typedef long long ll;
typedef pair<int,int> pa;
const int maxm=1<<30;

ll dp[35][(1<<4)+10];
int n;
vector<pa> state;
void dfs(int s,int now,int pre);

int main(){
    ios_base::sync_with_stdio(false);
    dfs(0,0,0);
    while(cin>>n&&n!=-1){
        if(n&1)
            cout<<0<<endl;
        else{

            memset(dp,0,sizeof(dp[0])*(n+1));
            dp[0][(1<<3)-1]=1;

            for(int i=0;i<n;++i)
            for(int j=0;j<state.size();++j)
                dp[i+1][state[j].second]+=dp[i][state[j].first];

            cout<<dp[n][(1<<3)-1]<<endl;
        }
    }
    return 0;
}

void dfs(int s, int now, int pre){
    if(s<=3){
        if(s==3)
            state.push_back(make_pair(pre,now));
        else{
            dfs(s+2,now<<2|3,pre<<2|3);
            dfs(s+1,now<<1|1,pre<<1);
            dfs(s+1,now<<1,pre<<1|1);
        }
    }
}

        POJ 3420需要稍微用一点点小技巧,即矩阵快速幂

        使用矩阵快速幂的理由是状态转移方程实际上可以写成矩阵的形式,我们刚刚的转移方程是dp[i][s]=∑(ss∈K(s))dp[i-1][ss]

        我们的矩阵大小是(1<<m)×(1<<m),其中matrix[i][j]表示从状态i到状态j有多少种方式

        求初始矩阵的话,在dfs的时候变成matrix[pre][now]++就好

        然后对初始矩阵求个n次幂,当然,用快速幂做

        最后的答案是matrix[(1<<m)-1][(1<<m)-1]

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;

ll n,m;
struct Matrix{
    ll a[16][16];
    Matrix(bool identity=false){
        memset(a,0,sizeof a);
        if(identity)
        for(int i=0;i<16;++i)
        a[i][i]=1;
    }
    Matrix(const Matrix& m){
        for(int i=0;i<16;++i)
        for(int j=0;j<16;++j)
            a[i][j]=m.a[i][j];
    }
    Matrix operator* (const Matrix& ma){
        Matrix te;
        for(int i=0;i<16;++i)
        for(int j=0;j<16;++j)
        if(a[i][j])
        for(int k=0;k<16;++k)
            te.a[i][k]+=a[i][j]*ma.a[j][k],te.a[i][k]%=m;

        return te;
    }
    Matrix qpow(int n){
        Matrix ans(true),te(*this);
        while(n){
            if(n&1)
                ans=ans*te;
            te=te*te;
            n>>=1;
        }
        return ans;
    }
}ma;
void dfs(int s=0,int now=0,int pre=0);

int main(){
    ios_base::sync_with_stdio(false);
    dfs();
    while(cin>>n>>m&&n+m)
        cout<<(ma.qpow(n)).a[(1<<4)-1][(1<<4)-1]<<endl;
    return 0;
}

void dfs(int s, int now, int pre){
    if(s<=4)
    if(s==4)
        ma.a[pre][now]++;
    else{
        dfs(s+2,now<<2|3,pre<<2|3);
        dfs(s+1,now<<1,pre<<1|1);
        dfs(s+1,now<<1|1,pre<<1);
    }
}

        感想:不知道看到这里的人有没有看我的100道动态规划——28,那道题是一个AC自动机上的动态规划,也一样运用到了矩阵快速幂,这里也是一个动态规划,也用到的矩阵快速幂,两者的共同之处就是状态转移方程都可以写成dp(state)=∑(ss∈K(state))dp(ss),其中K(state)是一个集合,K(state)={ss| p(state)},其中p()代表一个谓词函数。对于这样的是可以同矩阵快速幂的。。。等等,我似乎想到了以前一些同样用矩阵快速幂的题目。。。在这些题目的下面一定有共同之处。只是我现在还没办法用很形式化的语言把这个特点表示出来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值