引言:所谓轮廓线,不是某一行,或者某一列,而是指某一个特定轮廓的状态。
放置骨牌的约定:(保证放置有最优子结构)
假设我们正在放置第i行的骨牌,那么会有下面3种方式:
灰色表示已经有的骨牌,绿色表示新放置的骨牌。
每一种放置方法解释如下,假设当第i行的状态为x,第i-1行的状态为y:
- 第i行不放置,则前一行必须有放置的骨牌。x对应二进制位为0,y对应二进制位为1。
- 第i行竖放骨牌,则前一行必须为空。x对应二进制位为1,y对应二进制位为0。
- 第i行横向骨牌,则前一行必须两个位置均有骨牌,否则会产生空位。x对应二进制位为1,y对应二进制位为1。
简单的例子:
http://acm.hdu.edu.cn/showproblem.php?pid=1992
限制了棋盘宽度为4,数据不超过int,只有长度22以内满足答案。
手写状态找规律。
http://www.cnblogs.com/lzsz1212/archive/2012/05/02/2478839.html
把这个变成更一般的问题,如果不限制棋盘的宽,
hihocoder 骨牌问题讨论了窄棋盘情况下(2^min(n,m) 小于200),构造转移矩阵,快速幂的求法。
http://hihocoder.com/contest/hiho43/problem/1
转移矩阵构造法,y为i-1行状态,x为i行状态,dfs构造是K^2的复杂度,K=min(n,m)。
void dfs(int x,int y,int col){ if(col==K){ d[y][x]=1; return; } dfs(x<<1,(y<<1)|1,col+1); dfs((x<<1)|1,y<<1,col+1); if(col+2<=K){ dfs((x<<2)|3, (y<<2)|3, col+2); } }
那么复杂度就是k^3*logn,当k<=7时(2^7 = 128)的计算效率是可以接受的。#include <cstdio> #include <iostream> using namespace std; typedef long long LL; const int maxn = 1<<7; const int mod = 12357; int d[maxn][maxn]; int K,ALL; void dfs(int x,int y,int col){ if(col==K){ d[y][x]=1; return; } dfs(x<<1,(y<<1)|1,col+1); dfs((x<<1)|1,y<<1,col+1); if(col+2<=K){ dfs((x<<2)|3, (y<<2)|3, col+2); } } void mul(int a[][maxn],int b[][maxn],int c[][maxn]){ for(int i=0;i<ALL;i++){ for(int j=0;j<ALL;j++){ c[i][j]=0; } } LL t; for(int i=0;i<ALL;i++){ for(int j=0;j<ALL;j++){ if(!a[i][j])continue; for(int k=0;k<ALL;k++){ if(b[j][k]){ t=(LL)a[i][j]*b[j][k]; t+=c[i][k]; c[i][k]=t%mod; } } } } } void cpy(int a[][maxn],int b[][maxn]){ for(int i=0;i<ALL;i++){ for(int j=0;j<ALL;j++){ a[i][j]=b[i][j]; } } } void E(int a[][maxn]){ for(int i=0;i<ALL;i++){ a[i][i]=1; for(int j=i+1;j<ALL;j++){ a[i][j]=a[j][i]=0; } } } int e[maxn][maxn],tmp[maxn][maxn]; int main() { // freopen("data.in","r",stdin); int n; scanf("%d%d",&K,&n); dfs(0,0,0); ALL=1<<K; E(e); while(n>0){ if(n&1) { mul(e,d,tmp); cpy(e,tmp); } mul(d,d,tmp); cpy(d,tmp); n>>=1; } printf("%d\n",e[ALL-1][ALL-1]); return 0; }
uva11270同此题 ,大白书精讲,但是n*m<101,棋盘可能不是窄棋盘,k^3*logn矩阵运算会超时。
考虑到n<=10,可以构造2^n*n个转移方程,m轮递求解。
bfs可以避免访问不能求解的状态。状态st=k4k3k2k1k0,ki表示一个二进制位,有方块就为1,否则为0。
#include <cstdio> #include <iostream> #include <cstring> using namespace std; typedef long long LL; typedef LL type; typedef pair<int,LL> pil; #define mp make_pair #define FF first #define SS second const int maxn = 1<<10; int p[2][maxn]; pil q[2][maxn]; int tail[2]; void init(int cur){ memset(p[cur],-1,sizeof p[cur]); tail[cur]=0; } int main() { int n,m,cur,st,pos,nst,hi; LL cnt; while(scanf("%d%d",&m,&n)!=EOF){ if(m>n) swap(m,n); cur=0; st = (1<<m)-1; hi = 1<<(m-1); init(cur); p[0][st]=tail[cur]; pos = tail[cur]++; cnt = 1; q[cur][pos]=mp(st,cnt); for(int i=0;i<n;i++){ for(int j=0;j<m;j++,cur^=1){ init(cur^1); for(int k=tail[cur]-1;k>=0;k--){ st = q[cur][k].FF; cnt = q[cur][k].SS; if(st & hi){ nst = (st^hi)<<1; if((pos=p[cur^1][nst])==-1){ pos = tail[cur^1]++; p[cur^1][nst] = pos; q[cur^1][pos]=mp(nst,cnt); }else{ q[cur^1][pos].SS += cnt; } if(j && !(st&1)){ nst = ((st^hi)<<1)|3; if((pos=p[cur^1][nst])==-1){ pos = tail[cur^1]++; p[cur^1][nst] = pos; q[cur^1][pos]=mp(nst,cnt); }else{ q[cur^1][pos].SS += cnt; } } }else{ nst = (st<<1)|1; if((pos=p[cur^1][nst])==-1){ pos = tail[cur^1]++; p[cur^1][nst] = pos; q[cur^1][pos]=mp(nst,cnt); }else{ q[cur^1][pos].SS += cnt; } } } } } st = (1<<m)-1; pos = p[cur][st]; cnt = q[cur][pos].SS; printf("%lld\n",cnt); } return 0; }
对于更复杂的插头DP问题后面再讨论