1. 程式人生 > >2018.12.22【NOIP提高組】模擬B組 JZOJ 3511 cza的蛋糕

2018.12.22【NOIP提高組】模擬B組 JZOJ 3511 cza的蛋糕

大意

給定一個 n × m n\times m 的矩陣,其中一些點已經被覆蓋,現在要用 1 × 2

1\times 2 的長方形去覆蓋這個矩陣(可以90度翻轉),問放到不能再放的最少長方形放置數。

資料範圍:
對於30%的資料 N 5 M

5 N\leq 5,M\leq 5
對於100%的資料 N 70 M
7 N\leq 70, M\leq 7


思路

10%玄學

程式碼1

#include<algorithm>
#include<cstdio>
using namespace std;int n,m;
signed main()
{
	freopen("cake.in","r",stdin);
	freopen("cake.out","w",stdout);
	scanf("%d%d",&n,&m);
	printf("%d",max(n,m));
}

30%暴搜

程式碼2

#include<cstdio>
#include<iostream>
using namespace std;int n,m,ans=0x3f3f3f3f;
char c;
bool ok[71][9];
inline char Getchar()
{
    static char buf[100000],*p1=buf+100000,*pend=buf+100000;
    if(p1==pend)
	{
        p1=buf; pend=buf+fread(buf,1,100000,stdin);
        if (pend==p1) return -1;
    }
    return *p1++;
}
inline bool check()
{
	for(register int i=1;i<=n;i++)
	 for(register int j=1;j<=m;j++)
	 if(!ok[i][j])
	  if(!ok[i+1][j]||!ok[i][j+1]) return false;
	return true;
}
inline void dfs(register int now,register int x,register int y)
//線上的 過程 深度優先搜尋 (記錄整型 當前使用的長方形個數,記錄整型 當前是第幾行,記錄整型 當前是第幾個)
{
	if(now>=ans) return;//最優性剪枝
	if(x==n&&y==m)
	{
		if(check())ans=now;//儲存答案
		return;
	}
	if(ok[x][y])//已被覆蓋
	{
		if(y==m) dfs(now,x+1,1);//下一行
		else dfs(now,x,y+1);//下一個
		return;
	}
	ok[x][y]=true;//填充
	if(!ok[x][y+1]&&y<m)//後面那格可以填
	{
		ok[x][y+1]=true;//填
		dfs(now+1,x,y+1);
		ok[x][y+1]=false;//回溯
	}
	if(!ok[x+1][y]&&x<n)//下面那格可以填
	{
		ok[x+1][y]=true;//填
		if(y==m) dfs(now+1,x+1,1);
		else dfs(now+1,x,y+1);//判斷是否填到邊界
		ok[x+1][y]=false;
	}
	ok[x][y]=false;
	if(ok[x-1][y]||ok[x][y-1])//可以空
	{
		if(y==m) dfs(now,x+1,1);//直接空掉這格
			else dfs(now,x,y+1);
	}
}
signed main()
{
	freopen("cake.in","r",stdin);
	freopen("cake.out","w",stdout);
	cin>>n>>m;
	for(register int i=1;i<=n;i++)
	{
		ok[i][0]=ok[i][m+1]=1;
		for(register int j=1;j<=m;j++)
		{
			cin>>c;
			ok[0][j]=ok[n+1][j]=1;//周圍不能再填
			if(c=='*') ok[i][j]=1;//標記
		}
	}
	dfs(0,1,1);//搜尋
	cout<<ans;
}

我們發現這樣的程式碼沒有記憶化,於是乎我們就想到了記憶化,但是這樣由於狀態很多沒法表示,於是就可以用狀態壓縮每一行,然後進行狀壓 d p dp (記憶化搜尋)

AC程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#define ri register int
using namespace std;
int p[11],f[71][131][131],n,m;
char c;
int i,j,l,a[72],s,ans=0x3f3f3f3f;
inline void cge(ri k,ri x, ri y,ri z,ri t)
//線上的 過程 修改(當前填補的列數,上一行的狀態,這一行的狀態,下一行的狀態,當前步數)
{
	if(k>0&&((x&p[k-1])==0)&&((y&p[k-1])==0)) return;//若出現縱的兩個空位,不合法,退出
	if(k>1&&((y&p[k-1])==0)&&((y&p[k-2])==0)) return;//若出現橫的兩個空位,不合法,退出
	if(k==m)//搜尋完畢
	{
		f[i][y][z]=min(f[i][y][z],f[i-1][j][l]+t);//狀態轉移
		return;
	}
	cge(k+1,x,y,z,t);//這一格不填
	if(((z&p[k])==0)&&((y&p[k])==0))//能填縱的
	cge(k+1,x,y|p[k],z|p[k],t+1);//這一行的和下一行的都要修改
	if(k<m-1&&((y&p[k])==0)&&((y&p[k+1])==0))//能填橫的
	cge(k+2,x,y|p[k]|p[k+1],z,t+1);//修改這一行的兩個值
}
signed main()
{
	freopen("cake.in","r",stdin);
	freopen("cake.out","w",stdout);
	cin>>n>>m;
	for(i=1;i<=n;i++)
	{
		int x=0;
		for(j=1;j<=m;j++)
		{
			cin>>c;
			x<<=1;
			if(c=='*') x++;
		}
		a[i]=x;
	}
	memset(f,0x3f3f3f3f,sizeof(f));
	p[0]=1;
	for(i=1;i<10;i++) p[i]=p[i-1]<<1;
	s=p[m]-1;
	f[0][s][a[1]]=0;
	for(i=1;i<=n;i++)
	for(j=0;j<=s;j++)
	for(l=0;l<=s;l++)
	if(f[i-1][j][l]<0x3f3f3f3f)
	cge(0,j,l,a[i+1],0);
	for(i=0;i<=s;i++)
	ans=min(ans,f[n][i][0]);
	cout<<ans;
}