BZOJ 3336 Black and White (插头DP)
题目大意:
给你一个n×m的网格,有一些格子已经被涂上了白色或者黑色,让你用黑色或白色填剩下的格子,且填好的网格必须保证:
1.对于任意2×2的子矩阵的4个格子,它们的颜色不能都相同
2.所有黑色的块连在一起,所有白色块连在一起(四联通)
2<=n,m<=8
搞了2天终于过了
参考了这位神犇的博客http://www.cnblogs.com/staginner/archive/2012/09/17/2688634.html
很duliu的一道插头DP
涂色需要满足的条件有很多神奇的性质
由于一个联通块在轮廓线里可能出现不止一次,如果用括号匹配做,会非常麻烦,三进制也不好用了,用4进制会过于恶心
“最小表示法”
这是表示轮廓线的另一种方法,名字叫“最小表示法”,其实和字符串的最小表示法没多大联系,因为轮廓线并不能像成环的字符串那样滚动
但思想是类似的
轮廓线上处于同一联通块的格子编号相同
用尽可能小的数来表示状态,比如在某次状态更新后,新的状态表示为23333,那么重新编码后,状态变成了12222
可以用一个桶来实现
通过最小表示法,达到了我们“动态规划”合并等价状态的需要
然后把重新编码后的状态压缩成一个数,我用了十进制,虽然慢一点但特别好调
利用卓越的哈希技术,我们就能把这个数存下来了
由于2*2的田字形不能都同色,所以需要额外记录左上方格子的颜色
还要记录每个格子的颜色
由于相邻的相同颜色的格子必然处于同一联通块中
所以我们只记录第一个格子的颜色,后面格子的颜色就可以利用上面的性质直接推出来了
现在,我们能表示出轮廓线了
等等,左上,上,左,还要它自己,一共4个格子,岂不是要讨论2^4=16种情况??
复杂讨论的优化
优化1
由于题目中并没有对白色或黑色进行单独的额外限制,仅仅是一些格子必须填上白色或者黑色
所以我们在外层讨论向这个格子里填白色还是黑色就行了
里层函数只需分析 周围的格子的颜色 和 这个格子要填的颜色 是否同色,不同色为1,同色为0
这样优化掉了一维,只需要讨论8次了
优化2
以当前格子填黑色为例
1.上方格子无法在后续操作被合并
每当我们遍历到一个格子的时候,如果上方的格子是白色
和轮廓线上其他(除了左上)的白色格子都不连通
显然在轮廓线右移之后,上面的格子也不能再和其他的格子联通了
显然这种情况不合法
如果轮廓线上没有其他的白色格子,说明它是轮廓线上唯一一个联通块
由于2×2的格子不能同色,所以这种情况合法的充要条件是,现在的格子是最后两个格子(n,m)和(n,m-1)
2.左侧格子无法在后续操作被合并
显然这种情况只会发生在最后一排
为了减少讨论,我们无视这种情况
仅在遍历到最后一个格子(n,m)统计答案时,重新扫一遍轮廓线,同一颜色的格子应该都处于一个联通块中,即标号相同
我们又略去了左上方格子无法被联通的情况
特判
特判1
那么每一行开头的格子该怎么处理呢
把上一行的状态全都去掉第m+1格子,然后右移,把第一个格子的状态空出来
这样达到了保留左上方格子状态的目的
开头的格子需要额外的特判,讨论是否把上方的格子给堵住了
特判2
第一行该怎么办呢
搜2^m个的状态,检查是否合法,然后全扔到哈希表里
细节比较多
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N1 12
#define M1 50010
#define ll long long
#define uint unsigned int
#define il inline
#define jr 100
using namespace std; int T,n,m,now=,pst=,ne;
int bar[N1];
char str[N1][N1]; ll zip(int *a)
{
memset(bar,-,sizeof(bar));
ll ans=;int cnt=;
ans=a[];
for(int i=;i<=m+;i++)
{
if(bar[a[i]]==-) bar[a[i]]=++cnt;
ans=ans*+bar[a[i]];
}
return ans;
}
void unzip(int *a,int *b,ll s,int &ma)
{
for(int i=m+;i>=;i--)
a[i]=s%,s/=,ma=max(ma,a[i]);
b[]=a[];
for(int i=;i<=m+&&a[i];i++)
b[i]=(a[i]==a[i-])?b[i-]:b[i-]^;
} struct Hash{
int head[M1],nxt[M1],cte;
ll val[M1],to[M1];
void clr(){memset(head,,sizeof(head));cte=;}
void ae(int u,int v,int w)
{
cte++;to[cte]=v,val[cte]=w;
nxt[cte]=head[u],head[u]=cte;
}
int find(ll x)
{
ll y=x%jr,v;
for(int j=head[y];j;j=nxt[j]){
v=to[j];
if(v==x) return j;
}return ;
}
void ins(ll x,ll w)
{
int i=find(x);
if(!i) ae(x%jr,x,),i=cte;
val[i]+=w;
}
}h[]; int tmp[N1],a[N1],bin[N1];
int check(int l,int p,int r)
{
for(int i=;i<=l;i++)
if((tmp[i]==tmp[p])) return ;
for(int i=r;i<=m+;i++)
if((tmp[i]==tmp[p])) return ; //存在同一联通块
return ; //不存在
} ll ans=; void calc(int x,int y,int f)
{
ll s,t;
int typ[]={,},c[],ma,mi,fl,cnt,w;
for(int k=;k<jr;k++)
for(int p=h[pst].head[k];p;p=h[pst].nxt[p])
{
s=h[pst].to[p],ma=;
unzip(a,bin,s,ma);
c[]=(y!=)?(bin[y-]^f):,c[]=(y!=)?(bin[y]^f):,c[]=bin[y+]^f;
for(int i=;i<=m+;i++) tmp[i]=a[i];
/*if(x==2&&y==3)
mi=1;*/
w=h[pst].val[p];
if(y==){
if(!c[]) tmp[y]=tmp[y+];
else{
if(check(,y+,y+)) continue;
tmp[y]=,tmp[]=f;
}
}else if(c[]&&c[]&&c[]){ // 111 直接创建新联通块
tmp[y]=ma+;
}else if(!c[]&&c[]){ // 001 011
if(check(y-,y+,y+)){ //轮廓线上没有和上边相连的块
fl=;
for(int i=y-;i>=;i--)
if(bin[i]==bin[y+]) fl=;
for(int i=y+;i<=m+;i++)
if(bin[i]==bin[y+]) fl=; //轮廓线上存在其他1联通块,无法合并
if(!fl) continue;
if(!(x==n&&y==m-)&&!(x==n&&y==m)) continue; //不是最后两个格子,不合法
}
tmp[y]=tmp[y-];
}else if(c[]&&!c[]){ // 100 110 左上的点不用关心,必然连接左边或者上边,且是从上往下遍历的,直接合并即可,如果不合法,也可以最后再检查
tmp[y]=tmp[y+];
}else if(!c[]&&c[]&&!c[]){ // 010 由于之前001 011里保证过上方格子能联通,这次就不用再检查左上方了,合并联通块,如果没联通,除非是最后一个点,否则不合法
tmp[y]=tmp[y+]=tmp[y-]=min(tmp[y+],tmp[y-]);
for(int i=y+;i<=m+;i++) //合并联通块
if(a[i]==a[y+]) tmp[i]=tmp[y];
for(int i=y-;i>=;i--) //合并联通块
if(a[i]==a[y-]) tmp[i]=tmp[y];
}else if(c[]&&!c[]&&c[]){ //
if(x==n&&y==m) continue; //最后一个点,必然不合法
if(check(y-,y+,y+)){continue;} //即将越过这个点,如果没联通,之后也不能再联通了
tmp[y]=ma+;
}else{ // 000
continue;
}
t=zip(tmp);
if(x==n&&y==m)
{
cnt=,ma=;
unzip(a,bin,t,ma);
memset(bar,,sizeof(bar));
for(int i=;i<=m+;i++)
if(!bar[a[i]]) bar[a[i]]=,cnt++;
if(cnt<=)
ans+=h[pst].val[p];
continue;
}
h[now].ins(t,h[pst].val[p]);
}
}
void Enter()
{
h[now].clr();ll s,t;int ma;
for(int k=;k<jr;k++)
for(int p=h[pst].head[k];p;p=h[pst].nxt[p])
{
s=h[pst].to[p],ma=;
unzip(a,bin,s,ma);
for(int i=;i<=m+;i++) tmp[i]=a[i];
for(int i=m+;i>=;i--) tmp[i]=tmp[i-];
tmp[]=tmp[];
t=zip(tmp);
h[now].ins(t,h[pst].val[p]);
}
swap(now,pst);
}
void Pre()
{
int fl,cnt;ll t;
h[now].clr();
for(int s=;s<(<<m);s++)
{
fl=,cnt=;
for(int i=;i<m;i++){
if((s&(<<i))&&str[][i+]=='o') {fl=;break;}
if(!(s&(<<i))&&str[][i+]=='#') {fl=;break;}
bin[i+]=(s>>i)&;
}
if(!fl) continue;
tmp[]=bin[],tmp[]=++cnt;
for(int i=;i<=m;i++){
if(bin[i]==bin[i-]) tmp[i+]=tmp[i];
else tmp[i+]=++cnt;
}tmp[]=tmp[];
t=zip(tmp);
h[now].ins(t,);
}
swap(now,pst);
} int main()
{
//freopen("t2.in","r",stdin);
scanf("%d",&T);
while(T--)
{
ans=;
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++)
scanf("%s",str[i]+);
Pre();
ne=-;
if(str[n][m]=='#') ne=;
if(str[n][m]=='o') ne=;
for(int i=;i<=n;i++)
{
for(int j=;j<=m;j++)
{
h[now].clr();
if(str[i][j]!='#')
calc(i,j,);
if(str[i][j]!='o')
calc(i,j,);
swap(now,pst);
}
Enter();
}
printf("%lld\n",ans);
}
return ;
}
BZOJ 3336 Black and White (插头DP)的更多相关文章
- bzoj 1187: [HNOI2007]神奇游乐园 插头dp
1187: [HNOI2007]神奇游乐园 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 668 Solved: 337[Submit][Statu ...
- 【BZOJ】2331: [SCOI2011]地板 插头DP
[题意]给定n*m的地板,有一些障碍格,要求用L型的方块不重不漏填满的方案数.L型方块是从一个方格向任意两个相邻方向延伸的方块,不能不延伸.n*m<=100. [算法]插头DP [题解]状态0表 ...
- BZOJ3336: Uva10572 Black and White(插头Dp)
解题思路: 分类讨论即可. 代码(懒得删Debug了): #include<map> #include<cstdio> #include<vector> #incl ...
- bzoj 2331: [SCOI2011]地板【插头dp】
一开始设计了四种状态,多了一种已经拐弯但是长度为0的情况,后来发现不用,设012表示没插头,没拐弯的插头,拐了弯的插头,然后转移的话12,21,22都不合法,剩下的转移脑补一下即可,ans只能在11, ...
- BZOJ.1210.[HNOI2004]邮递员(插头DP Hash 高精)
BZOJ 洛谷 http://www.cnblogs.com/LadyLex/p/7326874.html 插头DP.\(m+1\)个插头的状态需要用三进制表示:\(0\)表示无插头,\(1\)表示是 ...
- bzoj 2331: [SCOI2011]地板 插头DP
2331: [SCOI2011]地板 Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 541 Solved: 239[Submit][Status] D ...
- bzoj 1210 [HNOI2004] 邮递员 插头dp
插头dp板子题?? 搞了我一晚上,还tm全是抄的标程.. 还有高精,哈希混入,还是我比较弱,orz各种dalao 有不明白的可以去看原论文.. #include<cstdio> #incl ...
- bzoj 1814 Ural 1519 Formula 1 ——插头DP
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1814 普通的插头 DP .但是调了很久.注意如果合并两个 1 的话,不是 “把向右第一个 2 ...
- 【BZOJ】2310: ParkII 插头DP
[题意]给定m*n的整数矩阵,求经过所有点至多一次路径的最大数值和.n<=8,m<=100. [算法]插头DP [题解]最小表示法确实十分通用,处理简单路径问题只需要状态多加一位表示独立插 ...
随机推荐
- mongodb 和 mongoose 初探
mongodb MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 1. 安装相关 1.1 下载 官网下载地址 :官网下载社区版 1.2 安 ...
- BIO、NIO、AIO
一.基础概念 IO操作分为两步:1.发起IO请求:2.执行具体IO操作: 同步和异步的区别是数据访问时进程是否阻塞或者说在执行真正IO操作时,数据能够立即返回就是异步,否则就是同步,同步和异步发生在I ...
- Apache 做反向代理服务器
apache做反向代理服务器 apache代理分为正向代理和反向代理: 1 正向代理: 客户端无法直接访问外部的web,需要在客户端所在的网络内架设一台代理服务器,客户端通过代理服务器访问外部的web ...
- 一个关于Class的小点
public 是公有 private 是私有 没有写就是private
- OpenLayers学习笔记3——使用jQuery UI美化界面设计
PC端软件在开发是有较多的界面库能够选择,比方DevExpress.BCG.DotNetBar等,能够非常方便快捷的开发出一些炫酷的界面,近期在学习OpenLayers.涉及到web前端开发,在设计界 ...
- Sort和UnSort的小技巧
Sort和UnSort的小技巧: 记录sortidx,对sortidx再从小到大排序就可以得到用于还原的unsortidx. 对于序列A: sort_idx = np.argsort(A) un_so ...
- 一个Python项目的创建架构
要进行Python项目的编写,很多人刚开始一筹莫展,不知道该如何去构建一个项目,现在粗略的描述一下一个项目的创建过程,供大家参考了解一下: 大家可以先忽略其中创建的函数 ,每个包的含义都有定义,大家可 ...
- 类扩展和category的小区别
类扩展可以给类声明新的变量(属性),但是方法的实现只能在.m中实现 category可以给类声明新的方法实例,但是不可以添加变量(属性)
- Android Fragment RecycleListView
1.新建SuperActivity package com.example.ting.criminalintentpractise; import android.os.Bundle;import a ...
- Oracle中的数据字典技术及常用数据字典总结
一.Oracle数据字典 数据字典是Oracle存放有关数据库信息的地方,其用途是用来描述数据的.比如一个表的创建者信息,创建时间信息,所属表空间信息,用户访问权限信息等.当用户在对数据库中的数据进行 ...