现在的位置: 首页 > 综合 > 正文

HDU 4012 Paint on a Wall

2019年02月24日 ⁄ 综合 ⁄ 共 2478字 ⁄ 字号 评论关闭

题目链接~~>

做题感悟:这题只能说很“ 经典 ” 。

解题思路:题意就是给你一个二行N例的一面墙,在墙上涂颜色,问你最少粉刷多少次,能达到给你的目标墙的状态,(每次粉刷只能粉刷出一个矩形,矩形可以是一行N例,也可以是二行N例);

思想:广度优先搜索(因为找最短路)
状态表示:
状态用二制位表示。例如当N=4时,就用8位二进制数表示状态如:
   1100
1010
‘‘0’’表示与目标状态颜色不一致,‘1’表示一致。
初始状态当然为:
0000
0000
因为和目标状态没有相同颜色。
目标状态自然为:
1111
1111
状态转移:
状态转移就是粉刷墙的过程,按传统套路很容易把粉刷的颜色也考虑进去。这样就走弯路了。
仔细想想会发现 每次粉刷,都会使一状态中的一些0变成1。从而越来越接近目标状态。
问题来了,每次粉刷多大?粉刷的大小受什么影响?
先分两种情况:
第一种是刷一行,在图上选一个点的颜色粉刷,分别向该点的左右粉刷,如果遇到和选取点颜色相同,则将当前状态该位的0变成1,表示与目标状态相同
第二种是刷两行,刷两行只考虑一行就可以,在其中一行寻解时,同时考虑同列的另一行,也就是在列相同的情况下判断两行的颜色,以及粉刷。
这样就可以从一个状态转移到另一个状态。
注:上述方法可以得到一个极大粉刷区域,每次状态转移全是转移最大粉刷区域,显然是不够准确的,算出最大粉刷区域后应该也将该区域的子集加到扩展队列中。求一个状态的子集的方法:整数temp的位数为当前状态 ,那么for(int j=temp;j;j=temp&(j-1));j就为子状态。可以自己模拟体会一下。
判重:
根据状态抽象,最多有2<16种状态,可以开一个bool型数组flag[1<<16];如flag[i]=1,则表示i二进制位表示的这个状态以经出现过。
代码:
#include<stdio.h>
#include<iostream>
#include<map>
#include<stack>
#include<string>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std ;
#define LEN   sizeof(struct node)
#define pret(a,b) memset(a,b,sizeof(a))
#define lld __int64
const double PI = 3.1415926 ;
const double INF = 999999999 ;
const double esp = 1e-4 ;
const lld  md= 2810778 ;
const int MX = 20 ;
int n ;
char s[MX] ;
bool vis[1<<16] ;
struct node
{
    int key,step ; // key 表状态 step 记录步数
} ;
int bfs()
{
    queue<node>q ;
    node curt,next ;
    while(!q.empty()) q.pop() ;
    memset(vis,false,sizeof(vis)) ;
    curt.key=curt.step=0 ;
    vis[0]=true ;
    q.push(curt) ;
    while(!q.empty())
    {
        curt=q.front() ;
        q.pop() ;
        for(int i=0 ;i<(n<<1) ;i++) // 遍历每一个点
        {
            int key=curt.key,ky=0 ;
            next.step=curt.step+1 ; // 步数加一
            if(key&(1<<i))      continue ;
            //   先单行左右扩展
            for(int j=i ;j<(i/n+1)*n ;j++)// 从当前节点向右扩展
            {
                if(key&(1<<j)) break ;// 遇到已经染色的就结束
                if(s[i]==s[j]) ky=ky|(1<<j) ; // 与起始颜色一样 ~> 染色
            }
            for(int j=i-1 ;j>=(i/n)*n ;j--) // 从当前节点向左扩展
            {
                if(key&(1<<j)) break ;
                if(s[i]==s[j]) ky=ky|(1<<j) ;
            }
            if((key|ky)==(1<<(n*2))-1) return next.step ; // 放在这可以减少时间
            for(int j=ky ;j ;j=key&(j-1))
            {
               if(!vis[key|j])
               {
                   vis[key|j]=true ;
                   next.key=key|j ;
                   q.push(next) ;
               }
            }
            // 以上为单行双向扩展
            if(i/n)  continue ;
            key=curt.key ; ky=0 ;
            if(key&(1<<(i+n))) continue ;
            for(int j=i ;j<n ;j++)
            {
                if((key&(1<<j))||(key&(1<<(j+n)))) break ;
                if(s[i]==s[j]) ky=ky|(1<<j) ;
                if(s[i]==s[j+n])ky=ky|(1<<(j+n)) ;
            }
            for(int j=i-1 ;j>=0 ;j--)
            {
                if((key&(1<<j))||(key&(1<<(j+n)))) break ;
                if(s[i]==s[j])  ky=ky|(1<<j) ;
                if(s[i]==s[j+n]) ky=ky|(1<<(j+n)) ;
            }
            if((key|ky)==(1<<(n*2))-1) return next.step ; 
            for(int j=ky ;j ;j=ky&(j-1)) // 切记要枚举子集:因为并不一定需要涂最大区域,如果涂了最大区域
            {                           //别的地方就没法连续涂了。
                if(!vis[key|j])
                {
                    vis[key|j]=true ;
                    next.key=key|j ;
                    q.push(next) ;
                }
            }
        }
    }
    return 0 ;
}
int main()
{
    int Tx,q=1 ;
    scanf("%d",&Tx) ;
    while(Tx--)
    {
        scanf("%d",&n) ;
        scanf("%s",s) ; // 用一维存储
        scanf("%s",s+n) ;
        printf("Case #%d: %d\n",q++,bfs()) ;
    }
    return 0 ;
}

 

抱歉!评论已关闭.