NKOJ 3253 (CQOI 2015) 标识设计(状压DP)

3253【CQOI2015】标识设计

问题描述

一家名字缩写为LLL的公司正在设计logo,他们的初步方案是在一张方格上放置3个L形的图案以及一些额外的装饰性图形。例如:

这里写图片描述(灰色区域表示装饰性图形)

3个L图案和装饰性图形均放置在方格之中,且必须占满方格。"L"的横竖笔画长短均可,但长度必须大于0(即不能退化为一条线段)。另外,为了使L图案醒目且容易辨别,设计师规定3个L形图案之间不能有重叠或交叉的部分。当然,L形图案也不能穿过装饰图形或与之重叠。
 现在设计师已经确定了所有装饰性图形的位置,希望你计算一下放置不同的L形图案总共可以设计出多少个logo。

输入格式

    输入文件第一行包含两个空格分开的正整数n和m,分别表示方格的行数和列数。
    接下来n行,每行m个字符,为"."或"#"。"#"表示该方格为装饰性图形,"."表示可以放置L图案的空白区域。

输出格式

    输出一个整数,为可能的logo总数。

样例输入

4 4
....
#...
....
..#.

样例输出

4

提示

样例解释:

这里写图片描述

    满足要求的logo只有这4种。注意,颜色只是为了便于说明,不影响方案数,因为在实际的logo中3个L图案将使用相同的色彩、纹理和光泽。

数据范围:
    对于30%的数据,n,m≤5。
    对于100%的数据,2≤n,m≤30。

首先,根据题意很容易想到利用DP来求解,那么问题在于如何确定状态。
我们可以将这三个“L”型的标记看成三条路径,然后问题就变成求三条互不相交的“L”型的路径。
观察“L”的特点我们发现,相当于是我在拓展这条路的时候只能向下或向右走,于是我们可以列出如下状态:
F[x][y][re][s][ty]
我们来解释一下这个状态,他表示的是我当前讨论到了(x,y)这个格子,还剩下re个“L”没有放,然后s是一个状压的结果,他表示在二进制下,当前在某些列存在向下走的路径,但还没有开始向右走,ty表示当前是否有路径正在向右走。

由于可能的转移只能是路径向下或向右延伸,因此我们可以推出转移方式(以下可以直接看代码):
若当前格子在一条向下走的路径上,那么他有向下和向右两种转移。
若当前格子在一条向右走的路径上,那么他有向右和停止两种转移。
若当前格子不在路径上,并且res>0&&y≠m,那么他可以作为一条路径的起点。
若当前格子是障碍格,并且处在一条路径上,那么返回0。
若当前格子同时在一条向右走的路径和向下走的路径上,那么返回0。
若当前y>m,跳到下一行。
若当前x>n,但还有“L”没放或者还有路径在向下走,那么返回0,否则返回1。

从上到下,从左到右,逐行逐列DP即可。

最后我们来看看s表示的状态,由于我们最多只需要放3个“L”,那么我们最多同时存在三列有向下走的路径,因此s的状态总数的最大值等于4525种,貌似还没有这么多,忽略。因此我们可以提前离散化。

于是我们可以用记忆化搜索的形式来写,具体情况参见代码。

代码写的很渣,亟待改进

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#define N 32
#define M 5000
#define ll long long
using namespace std;
ll n,m,F[N][N][4][M][2];
char G[N][N];
map<ll,ll>Q;
ll R[M],cnt;
ll DP(ll x,ll y,ll re,ll s,ll ty)//讨论到(x,y),还剩下 res个L没放,当前状态是s,表示向下的路径的位置,ty表示是否处在向右的路径上 
{
    ll i,j,k,tmp=0,t=R[s];//离散化回来 
    if(F[x][y][re][s][ty]>-1)return F[x][y][re][s][ty];//记忆化搜索 
    if(x>n)
    {
        if((!re)&&(!t))return F[x][y][re][s][ty]=1;//L已经放完并且都已经完成了向右走,返回1 
        return F[x][y][re][s][ty]=0;//否则返回0 
    }
    if(y>m)
    {
        if(ty)return F[x][y][re][s][ty]=0;//如果当前处在向右的路径上,返回0 
        return F[x][y][re][s][ty]=DP(x+1,1,re,s,0);//否则跳到下一行继续dp 
    }
    if((1<<y-1)&t)//如果当前格子在一条向下的路径上 
    {
        if(G[x][y]=='#'||ty)return F[x][y][re][s][ty]=0;//如果他同时在一条向右的路径上,或者是障碍,返回0 
        return F[x][y][re][s][ty]=DP(x,y+1,re,s,0)+DP(x,y+1,re,Q[t^(1<<y-1)],1);//否则返回这条路径向右走和向下走的方案和(如果开始向右走,那么不能再向下走,记变状态) 
    }
    if(ty)//如果当前格子在一条向右的路径上 
    {
        if(G[x][y]=='#')return F[x][y][re][s][ty]=0;//如果当前格子是障碍,返回0 
        return F[x][y][re][s][ty]=DP(x,y+1,re,s,1)+DP(x,y+1,re,s,0);//否则返回这条路径终止和向右走的方案和 
    }
    tmp=DP(x,y+1,re,s,ty);//什么也不做,讨论下一格 
    if(re>0&&G[x][y]=='.'&&y<m)tmp+=DP(x,y+1,re-1,Q[t|(1<<y-1)],ty);//如果当前格子可以作为一条路径的起点,那么新增一条路径 
    return F[x][y][re][s][ty]=tmp;//返回当前的方案数 
}
int main()
{
    memset(F,-1,sizeof(F));
    ll i,j,k,t,tt,ttt;
    scanf("%lld%lld",&n,&m);
    for(i=1;i<=n;i++)scanf("%s",&G[i][1]);

    Q[0]=++cnt;R[cnt]=0;//离散化状态 
    for(i=1;i<=m;i++)
    {
        t=1<<i-1;
        Q[t]=++cnt;R[cnt]=t;
        for(j=i+1;j<=m;j++)
        {
            tt=t|(1<<j-1);
            Q[tt]=++cnt;R[cnt]=tt;
            for(k=j+1;k<=m;k++)
            {
                ttt=tt|(1<<k-1);
                Q[ttt]=++cnt;
                R[cnt]=ttt;
            }
        }
    }

    ll ans=DP(1,1,3,0,0);
    cout<<ans;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值