F. Clear The Matrix
You are given a matrix f with 4 rows and n columns. Each element of the matrix is either an asterisk (*) or a dot (.).
You may perform the following operation arbitrary number of times: choose a square submatrix of f with size k × k (where 1 ≤ k ≤ 4) and replace each element of the chosen submatrix with a dot. Choosing a submatrix of size k × k costs ak coins.
What is the minimum number of coins you have to pay to replace all asterisks with dots?
The first line contains one integer n (4 ≤ n ≤ 1000) — the number of columns in f.
The second line contains 4 integers a1, a2, a3, a4 (1 ≤ ai ≤ 1000) — the cost to replace the square submatrix of size 1 × 1, 2 × 2, 3 × 3 or 4 × 4, respectively.
Then four lines follow, each containing n characters and denoting a row of matrix f. Each character is either a dot or an asterisk.
Print one integer — the minimum number of coins to replace all asterisks with dots.
4 1 10 8 20 ***. ***. ***. ...*
9
7 2 1 8 2 .***... .***..* .***... ....*..
3
4 10 10 1 10 ***. *..* *..* .***
2
In the first example you can spend 8 coins to replace the submatrix 3 × 3 in the top-left corner, and 1 coin to replace the 1 × 1 submatrix in the bottom-right corner.
In the second example the best option is to replace the 4 × 4 submatrix containing columns 2 – 5, and the 2 × 2 submatrix consisting of rows 2 – 3 and columns 6 – 7.
In the third example you can select submatrix 3 × 3 in the top-left corner and then submatrix 3 × 3 consisting of rows 2 – 4 and columns 2 – 4.
大致题意:给你一个4*N的矩阵,矩阵中只含有'*'和'.',然后每次你可以选择1*1、2*2、3*3、4*4的子矩阵,把子矩阵中每个位置都变成'.'。对应每一个尺寸的矩阵会有一个权值,然后问你把整个矩阵变成'.'最少需要多少代价。
典型的状态压缩dp。经过分析可以发现,某一个状态与下一个状态的关系,只与后面的4*4的矩阵有关系,所以我们不妨用这个表示状态。dp[i][status]表示前面i列已经全部是'.',然后后面4*4的矩阵是status情况的时候的最少代价,0表示是'*',1表示是'*'。如果仔细思考,会发现,这题和15年安徽CCPC扫雷那题还是有一些类似的,同样是在矩阵中进行状态压缩。同样的,本题的转移也是类似,按照一列一列来,找到一种矩阵转换关系,是得第i+1列全为1,然后就可以转移到下一个状态。但是本题的关键就是如何枚举或者说构造这种子矩阵的选择方式。
由于总共只有四行,而且最小的子矩阵是1*1的,所以我们考虑以每一行为子矩阵的左下角,然后枚举在这个位置开始的矩阵的尺寸。显然最后一行可以放4种尺寸,第3行可以放前三种……枚举完毕之后,计算出代价值和转移之后的状态,如果满足第i+1行填满,那么就可以进行转移,并且把下一列加入下一个状态的表示中。这里,对于这个左下角的控制,同样可以考虑用位运算移位的操作对于左下角在第i行的,对应往右移(i-x)位,其中x为矩阵的尺寸。注意到i-x可以是负数,这就表示往左移,但是这里不能表示为往右移(x-i),具体原因玄学,调了好久才发现……
这样子总的时间复杂度是O(N*2^16*5*4*3*2),远远超过了1e9,但是玄学复杂度居然可以很玄学的过了,而且是181MS,不太慢。考虑到理论的复杂度有点高,我们又仔细分析了一下,可以发现,这里其实相关的格子只是最后4*3个格子即可,因为可能对第四列产生影响的只有4*4的子矩阵,而这种情况直接在转移的过程中可以顺带处理,即第四列与新加进来的下一个状态的那一列做一个或即可。由此,我们可以状态少四位,复杂度可以降到O(N*2^12*5*4*3*2),这样子的话在31MS内就可以通过。具体见代码:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1010
using namespace std;
int dp[2][1<<15],mat[5],w[5],n;
bool s[N][5];
char ch[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=4;i++)
scanf("%d",&w[i]);
for(int i=1;i<=4;i++)
{
scanf("%s",ch);
for(int j=0;j<n;j++)
s[j+1][i]=ch[j]=='.';
}
int s0=0;
mat[1]=1; mat[2]=51;
mat[3]=1911; mat[4]=65535;
memset(dp,INF,sizeof(dp));
for(int i=1;i<=4;i++)
for(int j=1;j<=3;j++)
if (s[j][i]) s0|=1<<((j-1)*4+i-1); //构造初始状态
int cur=1,pre=0;
dp[pre][s0]=0; w[0]=0;
for(int i=0;i<n;i++)
{
int nxt=0;
memset(dp[cur],INF,sizeof(dp[cur]));
for(int j=1;j<=4;j++)
if (s[i+4][j]) nxt|=1<<j-1;
for(int st=0;st<(1<<12);st++)
{
if (dp[pre][st]==INF) continue;
for(int a=0;a<=4;a++)
for(int b=0;b<=3;b++)
for(int c=0;c<=2;c++)
for(int d=0;d<=1;d++)
{
int cost=w[a]+w[b]+w[c]+w[d]; //枚举左下角放的子矩阵尺寸
int add=mat[a]<<(4-a)|mat[b]<<(3-b)|mat[c]<<(2-c)|mat[d]<<(1-d);
if (((st|add)&15)==15) //如果可以填满i+1列,那么转移
{
int nxt_st=(st>>4)|(add>>4)|(nxt<<8); //下一个状态加入
dp[cur][nxt_st]=min(dp[cur][nxt_st],dp[pre][st]+cost);
}
}
}
swap(cur,pre);
}
printf("%d\n",dp[pre][0]);
return 0;
}