【NOIP模拟】数格子

Description

这里写图片描述

Solution

一道很经典的题目的简单版。
很显然是状压DP。
首先4*n可以变成n*4,然后状态数就只有16个了。(1表示有凸起,0表示没有凸起)
设f[i][j]表示第i层的状态数为j的时候的方案数。
一开始可以先预处理一个a[i][j]表示状态i是否能转移到状态j。
如果能转移到,那么要具备两个条件:
1、i&j==0,这个很明显,如果两个都填1,那么就说明会对下一层造成影响。
2、pan(i+j)==1,pan(i)表示i转态里面是否有连续的次数为奇数的1,如果有就是不合法值为0,否则值为1。为什么上下两层转态加起来一定要没有连续的奇数个1。首先,对于第一行来说这是必须的,然后对于后面的排来说如果上面有0,那么下面可以填1,如果有不填的情况就是上面有两个0,刚好可以横着放,导致下一层也有两个零。那么就是说两层放竖板的地方加起来不会有连续为奇数的情况。
那么之后DP完,再打个矩阵乘法就可以了。

Code

#include
#include
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100007;
int i,j,k,l,t,n,m,ans;
int er[10];
typedef long long ll;
struct ju{
    ll a[16][16];
    ju friend operator *(ju a,ju b){
        ju c;memset(c.a,0,sizeof(c.a));
        int i,j,k;
        fo(i,0,15){
            fo(j,0,15){
                fo(k,0,15){
                    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%m;
                }
            }
        }
        return c;
    }
}a,b;
bool pan(int x){
    int i,j=0;
    fo(i,1,4){
        if((x&er[i])==0){
            if(j%2)return 0;
            j=0;
        }
        else j++;
    }
    if(j%2)return 0;
    return 1;
}
bool guhou(int x,int y){
    if(((x&y)==0)&&pan(x+y))return 1;return 0;
}
ju qsm(ju x,int y){
    ju z;
    memset(z.a,0,sizeof(z.a));fo(i,0,15)z.a[i][i]=1;
    while(y){
        if(y&1)z=z*x;
        x=x*x;
        y/=2;
    }
    return z;
}
int main(){
    fo(i,1,9)er[i]=1<<(i-1);
    fo(i,0,15){
        fo(j,0,15){
            if(guhou(i,j)){
                a.a[i][j]=1;
            }
        }
    }
    while(1){
        scanf("%d%d",&n,&m);
        if(!n&&!m)break;
        memset(b.a,0,sizeof(b.a));
        fo(i,0,15)if(pan(i))b.a[0][i]=1;
        b=qsm(a,n)*b;
        printf("%lld\n",b.a[0][0]);
    }
}

你可能感兴趣的:(noip,DP,状态压缩DP,矩阵乘法)