CQOI2007矩形

文章目录

题目

思路

可以用块状插头 d p dp dp

一开始让 ( 1 , 1 ) (1,1) (1,1) 为黑色就变成了:

求一个黑色连通块方案数并且有白色挨边界的方案数

f i , j , S , c , t , w f_{i,j,S,c,t,w} fi,j,S,c,t,w:当前到了 ( i , j ) (i,j) (i,j) 的扫描块连通情况为 S S S (最小表示法),颜色情况为 c c c ,是否有白色挨边界 t t t ,是否还能放白色 w w w

其中 t , w t,w t,w 取值都是 01 01 01 c c c 是 2 进制,
S S S 由于地图很小,最多有 4 4 4 种取值用 8 进制

转移类似扫描线,但是要注意扫描线全白和全黑情况,以及独立连通块不合法

非打表 60~70ms 左右

还有一种扫描线的思路就是类似暴力用分割线

分割点拉出来构成(n-1)*(m-1)的矩形

然后根据CDQ论文可以在边界放两个端点插头,加一维(0,1,2)表示边界上放了几个端点插头,dp即可

时间跟上面差不多(主要跟写法有关)

可以做做 Black&White 那道题目,思路相似可以练习

代码

#include<set>    
#include<map>    
#include<stack>    
#include<ctime>    
#include<cstdio>    
#include<queue>    
#include<cmath>    
#include<vector>    
#include<cstring>   
#include<climits>    
#include<iostream>   
#include<algorithm>
using namespace std;
#define LL long long
#define ULL unsigned long long
int read(){
    int f=1,x=0;char c=getchar();
    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f*x;
}
#define MAXN 12
#define Mod 199999
#define INF 0x3f3f3f3f
int n,m,nw,lst,nx,ny,siz[2];
int head[Mod+5],nxt[Mod+5];
int tag[2][Mod+5],f[2][Mod+5],sta[2][Mod+5],col[2][Mod+5],wh[2][Mod+5];
void Add(int s,int cnt,int c,int t,int w){
	int tmp=s%Mod;
	//printf("s:%d c:%d t:%d ",s,c,t);
	for(int i=head[tmp];i;i=nxt[i])
		if(sta[nw][i]==s&&col[nw][i]==c&&tag[nw][i]==t&&wh[nw][i]==w){
			f[nw][i]+=cnt;
		//	printf("cnt:%d\n",f[nw][i]);
			return ;
		}
	nxt[++siz[nw]]=head[tmp],head[tmp]=siz[nw],tag[nw][siz[nw]]=t;
	sta[nw][siz[nw]]=s,f[nw][siz[nw]]=cnt,col[nw][siz[nw]]=c,wh[nw][siz[nw]]=w;
	//printf("cnt:%d\n",f[nw][siz[nw]]);
	return ;
}
int a[MAXN+5],num[MAXN+5];
void Open(int s){
	for(int i=0;i<m;i++)
		a[i]=s&7,s>>=3;
	return ;
}
int Zip(){
	memset(num,-1,sizeof(num));
	int k=-1;
	int ret=0;
	for(int i=m-1;i>=0;i--){
		if(num[a[i]]==-1)
			num[a[i]]=++k;
		ret=(ret<<3)|num[a[i]];
	}
	return ret;
}
void Replace(int x,int y){
	for(int i=0;i<m;i++)
		if(a[i]==x)
			a[i]=y;
	return ;
}
void Fill(int i,int j,int c){
	//puts("");
	//printf("%d %d %d\n",i,j,c);
	for(int k=1;k<=siz[lst];k++){
		int s=sta[lst][k],cc=col[lst][k],cnt=f[lst][k],tt=tag[lst][k],w=wh[lst][k];
		int l=0,u=0;
		if(i>0)
			u=(((cc>>j)&1)==c);
		if(j>0)
			l=(((cc>>(j-1))&1)==c);
		Open(s);
		if(cc==0&&c==1&&!(i==0&&j==0))//全白不放黑
			continue;
		if(cc==(1<<m)-1&&c==0&&w)//全黑可放白
			continue;
		if(i>0&&!u){
			int s1=0,s2=0;
			for(int t=0;t<m;t++){
				if(a[t]==a[j])
					s1++;
				if(((cc>>t)&1)!=c)
					s2++;
			}
			if(s1==1&&s2>1)//独立连通块
				continue;
		}
		if(l&&u){
			if(a[j]!=a[j-1])
				Replace(a[j],a[j-1]);
		}
		else if(l&&!u)
			a[j]=a[j-1];
		else if(!l&&!u)
			a[j]=m;//else same
		if(c)
			cc|=(1<<j);
		else
			cc&=~(1<<j);
		Add(Zip(),cnt,cc,tt|(!c&&(!i||!j||i==n-1||j==m-1)),w|(!c));
	}
	return ;
}
void Dp(){
	nw=0;
	memset(head,0,sizeof(head)),siz[nw]=0;
	Add(0,1,0,0,0);
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++){
			lst=nw,nw^=1;
			memset(head,0,sizeof(head)),siz[nw]=0;
			if(i||j)//(0,0)必放黑
				Fill(i,j,0);
			Fill(i,j,1);
		}
	int ret=0;
	for(int i=1;i<=siz[nw];i++){
		int tmp=0,tt=tag[nw][i];
		if(!tt) continue;
		Open(sta[nw][i]);
		for(int j=0;j<m;j++)
			tmp=max(tmp,a[j]);
		if(tmp>1)
			continue;
		ret+=f[nw][i];
	}
	printf("%d\n",ret);
	return ;
}
int main(){
	n=read(),m=read();
	Dp();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值