NOIP提高组【JZOJ4787】数格子

来源:互联网 发布:知乎 南北战争的武器 编辑:程序博客网 时间:2024/05/20 02:29

Description

这里写图片描述

Data Constraint

这里写图片描述

每个测试点数据组数不超过10组

Solution

这是道简单的状态压缩dp。我们设出f[i][j]表示现在做到第i行,第i行的状态为j的方案数。第k位为1表示这里放了一块打竖的牌,这个牌的最上一行为k,第k位为0表示这里它的一行同一个地方放了个打竖的牌,或者这一行放了个打横的牌。显然f[i][j]可由f[i-1][k]转移过来。前提是若k的第x位为1,那么j的这一位必须为0;若k的第x位为0,则不对j作要求。但除了j必须为0的位置以外,其他位要想为0必须为相邻的两个同时为0(考虑到列数只有4,一行最多只有两个打横的牌)。这样我们就做出60分。

我们发现每次有第i行转移到第i+1行,和第i+1行转移到第i+2行的转移方向是一样的,即在第i行状态j可以转移到第i+1行状态k,那么第i+1行状态j可以转移到第i+2行状态k。所以我们用16*16的矩阵记录下每个状态可以转移到那些状态,用矩阵快速幂即可。时间复杂度O(logN212)。

代码

#include<iostream>#include<cstring>#include<cstdio>#include<cmath>#include<algorithm>#define ll long longusing namespace std;const int maxn=10,maxn1=20;int f[maxn][maxn1],n,m,i,t,j,k,l,p,p1;struct code{    ll a[maxn1][maxn1];}a,b;code c(code x,code y){    int i,j,k;code z;    memset(z.a,0,sizeof(z.a));    for (i=0;i<=15;i++)        for (j=0;j<=15;j++)            for (k=0;k<=15;k++)                z.a[i][j]=(z.a[i][j]+x.a[i][k]*y.a[k][j]%m)%m;          return z;}code mi(int x){    if (x==1) return a;    code t=mi(x/2);    if (x%2) return c(c(t,t),a);return c(t,t);}int main(){//  freopen("data.in","r",stdin);    while (1){        scanf("%d%d",&n,&m);        if (!n) break;        t=15;        f[1][15]=1;f[1][12]=1;f[1][0]=1;f[1][3]=1;f[1][9]=1;        for (j=0;j<=15;j++){            k=15;            for (l=0;l<=3;l++)                if ((1<<l)&j) k-=(1<<l);            if (!j) a.a[j][15]=1,a.a[j][12]=1,a.a[j][3]=1,a.a[j][0]=1,a.a[j][9]=1;                      else{                a.a[j][k]=1;                for (l=0;l<=2;l++)                    if (!((1<<l)&j) && !((1<<(l+1))&j)) a.a[j][k-(1<<l)-(1<<(l+1))]=1;            }        }        if (n>1){            b=mi(n-1);            memset(f[2],0,sizeof(f[2]));            for (j=0;j<=15;j++)                for (k=0;k<=15;k++)                    f[2][j]=(f[2][j]+f[1][k]*b.a[k][j]%m)%m;            t=f[2][0];        }else t=f[1][0];        printf("%d\n",t);    }}
3 0
原创粉丝点击