CodeForces 903F Clear The Matrix (状态压缩dp)

F. Clear The Matrix

time limit per test:1 second
memory limit per test:256 megabytes
input:standard input
output:standard output

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?

Input

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.

Output

Print one integer — the minimum number of coins to replace all asterisks with dots.

Examples
Input
4
1 10 8 20
***.
***.
***.
...*
Output
9
Input
7
2 1 8 2
.***...
.***..*
.***...
....*..
Output
3
Input
4
10 10 1 10
***.
*..*
*..*
.***
Output
2
Note

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值