輪廓線DP(插頭DP 裸 經典骨牌)
阿新 • • 發佈:2019-02-17
引言:所謂輪廓線,不是某一行,或者某一列,而是指某一個特定輪廓的狀態。
放置骨牌的約定:(保證放置有最優子結構)
假設我們正在放置第i行的骨牌,那麼會有下面3種方式:
灰色表示已經有的骨牌,綠色表示新放置的骨牌。
每一種放置方法解釋如下,假設當第i行的狀態為x,第i-1行的狀態為y:
簡單的例子:
限制了棋盤寬度為4,資料不超過int,只有長度22以內滿足答案。
手寫狀態找規律。
把這個變成更一般的問題,如果不限制棋盤的寬,
hihocoder 骨牌問題討論了窄棋盤情況下(2^min(n,m) 小於200),構造轉移矩陣,快速冪的求法。
轉移矩陣構造法,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問題後面再討論