1 条题解

  • 0
    @ 2026-4-28 10:25:46

    注意一个题意理解上的问题:每一个窗口只能被一个胶带纸贴着,比如下面这份输入数据:

    5 5
    .#.#.
    #####
    .#.#.
    #####
    .#.#.
    

    Mirko 显然可以用四个胶带全部贴上,但是因为他只能“将一横排或一纵列连续的窗口合上”,而被贴过的窗口已经是合上的,所以在贴新胶带的时候变成不连续的了,因此应该输出 88 而不是 44

    用记忆化搜索做的轮廓线 dp,设 dpi,j,Sdp_{i,j,S} 为考虑到第 ii 行第 jj 列,前面的 mm 个空是否选择竖着涂的状态是 SS,剩下最少需要几个胶带。具体的思路已经在注释里了。

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,M;
    int b[1005][15];
    int dp[1005][15][2000];
    int nxti(int i,int j){
    	if(j<m)return i;
    	return i+1;
    }
    int nxtj(int i,int j){
    	if(j<m)return j+1;
    	return 1;
    }
    int lsti(int i,int j){
    	if(j>1)return i;
    	return i-1;
    }
    int lstj(int i,int j){
    	if(j>1)return j-1;
    	return m;
    }
    int dfs(int i,int j,int S){//考虑到第 i 行第 j 列,前面的 m 个空竖着涂的状态是 S,剩下最少要几个胶带
    	S&=M;
    	if(i>n)return 0;
    	if(dp[i][j][S]!=-1)return dp[i][j][S];
    	int ans=114514;
    	int ni=nxti(i,j),nj=nxtj(i,j);
    	int li=lsti(i,j),lj=lstj(i,j);
    	if(!b[i][j])return dp[i][j][S]=dfs(ni,nj,S<<1);
    	//先看能不能竖着涂 
    	//如果上面有竖着涂的,可以选择继续竖着涂
    	if(S&(1<<(m-1)))ans=min(ans,dfs(ni,nj,(S<<1)|1));
    	//如果上面没有竖着涂的,可以自己开始竖着涂
    	else ans=min(ans,1+dfs(ni,nj,(S<<1)|1));
    	//再看能不能横着涂,要看前一个有没有选择横着涂 
    	if(i!=li)ans=min(ans,1+dfs(ni,nj,S<<1));//如果是一行开头左边啥都没有只能新开一个横着涂
    	else if(b[li][lj]&&(S&1)==0)ans=min(ans,dfs(ni,nj,S<<1));//前一个需要涂但没有竖着涂,那就是横着的了
    	else ans=min(ans,1+dfs(ni,nj,S<<1));//不能接到左边,只能自己开始横着涂 
    	return dp[i][j][S]=ans; 
    }
    int main(){
    	ios::sync_with_stdio(0);cin.tie(0);
    	cin>>n>>m;
    	M=(1<<m)-1;
    	for(int i=0;i<=n;i++){
    		for(int j=0;j<(1<<m);j++){
    			for(int k=0;k<=m;k++)dp[i][k][j]=-1;
    		}
    	}
    	for(int i=1;i<=n;i++){
    		string s;cin>>s;
    		for(int j=0;j<m;j++){
    			if(s[j]=='#'){
    				b[i][j+1]=1;
    			}
    		}
    	}
    	cout<<dfs(1,1,0);
    	return 0;
    }
    
    • 1

    信息

    ID
    10840
    时间
    1000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者