hdu - 4332 - Constructing Chimney - 状态压缩dp

题意: http://acm.hdu.edu.cn/showproblem.php?pid=4332

有一块3*3的空地,要求在上面建一个建筑物,但是要求中间的那块必须空着,现在只有1*1*2的砖,问你要建成高度为N的建筑物共有多少种不同的方法。N<=1e9

思路:
看到这么大的N就应该想到用矩阵二分幂来求,那么很显然就是dp了,因为砖只有1*1*2的一种,在对一层进行排放的时候我们只需关心它的上一层的摆放情况就好了, 因此我们需要用一个8bit的二进制来记录上一层的状态,接下去我们就要构造出一个转移矩阵。先考虑一下复杂度:O(256^3*log(n) ),这样的复杂度有有可能会超时(但是事实是题目给的20s时限这样的复杂度也可以过)。这样我们就希望能优化上面的复杂度,logn的矩阵乘法是不能优化了,那么我们就想优化前面的256^3,看是否其中的有些状态是可以去掉的。考虑题目中的8个位置正好是矩阵的外圈,用8位的2进制表示时有对称性,如果将4个对称的部分合在一起,那么状态数就会变成只有256/4种(其实有70种),这样我们就可以将复杂度降低了。最后的工作就是构造出矩阵,然后用矩阵二分幂求解。 在构造矩阵的时候要注意去掉重复的。

#include <stdio.h>
#include <string.h>
#include <map>
#include <algorithm>

typedef __int64 LL ;
const LL Mod = 1000000007LL ;

const int m = (1 << 8) ;
const int mm = 70 ;
bool ok[m]  ;
int a[8] , N ;
int min[m] ;

struct Matrix{
    LL mat[mm][mm] ;
    void init(){
        memset( mat,  0 ,sizeof(mat) ) ;
    }
}M[31] , res ,mid ;
LL ans[mm] ;

bool is_ok(int s){
    if( s==0 )  return true ;
    if( s==m-1 )  return true ;
    int val[8] ;
    int i ;
    for(i=0;i<8;i++)
        if( s&a[i] )    val[i] = 1 ;
        else    val[i] = 0;
    i = 0 ;
    while( val[i] ) ++ i ;
    for(int ii=0;ii<8;ii++  ){
        if( val[ (ii+i)%8 ] == 0 )   continue ;
        if( ii+1<8 && val[ (ii+i+1)%8 ] ){
            ii ++ ;
        }
        else    return false ;
    }
    return true ;
}

int getmin( int n ){
    int val[8] ;
    for(int i=0;i<8;i++) {
        if( n&a[i] )    val[i] = 1 ;
        else    val[i] = 0 ;
    }
    int v = 0 ;
    int res = n ;
    for(int ii=0;ii<8;ii++)
        if( val[ (2+ii)%8 ] )   v |= a[ ii ]  ;
    if( res > v )   res = v ;
    v = 0 ;
    for(int ii=0;ii<8;ii++)
        if( val[ (4+ii)%8 ] )   v |= a[ ii ]  ;
    if( res > v )   res =v ;
    v = 0 ;
    for(int ii=0;ii<8;ii++)
        if( val[ (6+ii)%8 ] )   v |= a[ ii ]  ;
    if( res > v )   res =v ;
    return res ;
}

std::map<int , int> mp ;
bool vis[m + 10][m + 10]  ;

void deal(int sou , int to){
    int val[8]  ;
    for(int i=0;i<8;i++){
        if( to&a[i] )  val[i] = 1 ;
        else    val[i] = 0 ;
    }
    LL v1 , v2 ;
    v1 = sou ;
    for(int j=0; j<=6;j+=2 ){
        v2 = 0  ;
        for(int jj=0;jj<8;jj++){
            if( val[ (j+jj)%8 ] )  v2 |= a[jj]  ;
        }
        if( ( v1|v2 )==m-1 && ok[ v1&v2 ] && !vis[v1][v2] ){
            M[0].mat[ mp[ min[v1] ] ][ mp[ min[v2] ] ] ++ ;
            vis[v1][v2] = 1 ;
        }
    }
}

void build_matrix(){
    M[0].init() ;
    memset( vis , 0 , sizeof(vis) );
    std::map<int, int>::iterator it1 ,it2  ;

    for( it1 = mp.begin() ; it1!=mp.end() ; it1++ ){
        for( it2=mp.begin() ; it2!=mp.end() ; it2++ ){
            deal( it1->first , it2->first ) ;
        }
    }
    M[0].mat[mm-1][mm-1] = 2 ;
    for(int ii=1;ii<=30;ii++){
        for(int i=0;i<mm;i++){
                for(int j=0;j<mm;j++){
                    mid.mat[i][j] = 0 ;
                    for(int k=0;k<mm;k++){
                        mid.mat[i][j] = ( mid.mat[i][j] + M[ii-1].mat[i][k] * M[ii-1].mat[k][j] % Mod ) % Mod ;
                    }
                }
            }
        M[ii] = mid ;
    }
}

void init(){
    a[0] = 1 ;
    for(int i=1;i<8;i++)    a[i] = a[i-1]<<1 ;
    for(int i=0;i<m;i++)    min[i] = getmin(i)  ;
    for(int i=0;i<m;i++){
        ok[i] = is_ok( i ) ;
        mp[ min[i] ] = 1 ;
    }
    std::map<int,int>::iterator it = mp.begin() ;
    int ss = 0 ;
    for( it ; it != mp.end(); it ++ ){
        it->second = ss ++ ;
    }
    build_matrix() ;
}

void calc( int n ){
    memset( ans , 0 , sizeof(ans) )  ;
    int jj = 0 ;
    ans[ mm-1 ] = 1 ;
    LL CC[mm] ;
    while( n ){
        if( n&1 ){
            for(int i=0;i<mm;i++){
                CC[i] = 0;
                for(int j=0;j<mm;j++){
                    CC[i] = ( CC[i] + ans[j] * M[jj].mat[i][j]% Mod ) % Mod ;
                }
            }
            for(int i=0;i<mm;i++)  ans[i] = CC[i] ;
        }
        n >>= 1 ; jj ++ ;
    }
}

int main(){
    init() ;
    int T ;scanf("%d",&T) ;
    int cas = 0 ;
    while( T-- ){
        scanf("%d",&N) ;
        calc(N + 1) ;
        printf("Case %d: %I64d\n",++cas,ans[0] ) ;
    }
    return 0 ;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值