【题&结论(递归标连通块)】【搜索(IDA*)】NKOJ 2440 数字消除游戏

40 篇文章 0 订阅
7 篇文章 0 订阅

NKOJ 2440 数字消除游戏
时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 时限1000ms

问题描述
在一个n*n的方形棋盘上玩消除游戏,棋盘上布满了数字。
每一步,玩家可以任选一个数字x,用它填充坐标为(1,1)格子所在连通区域,该区域的数字都会变成x。(如果两个数字相同且相邻,我们称这两个数字连通。相邻是上下左右四方向)。
当整个棋盘的数字都相同时,就可以将整个棋盘上的数字消除掉,游戏结束。
问,最少需要几次操作就能消除所有数字。

输入格式
有若干组测试数据(不超过20组),对于每组测试数据:
第一行,一个整数n,表示棋盘的尺寸。
接下来一个n*n的数字矩阵,表示游戏的初始局面。
当输入的n==0时,输入结束。

输出格式
对于每组测试数据,输出一行,一个整数,表示最少需要的操作数。

样例输入
2
1 3
3 3
4
5 5 2 0
5 5 2 0
2 2 2 0
0 0 0 2
0

样例输出
1
3

提示
样例说明,对于第二组数据:
第1步 选数字2去填充,得到:
2 2 2 0
2 2 2 0
2 2 2 0
0 0 0 2
第2步 选数字0去填充,得到:
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 2
第3步 选数字2去填充,得到:
2 2 2 2
2 2 2 2
2 2 2 2
2 2 2 2
对于100%的数据,n<=8,
初始棋盘中最多有6种不同的数字

思路:
IDA*,h()表示除左上角连通块外的数字中,不同的数字共有多少种。
剪枝1:当当前步数+h()>d时可回溯。
剪枝2:改变数字后,若连通块没改变,回溯

优化:每次从左上角开始修改太费时,多加一个数组,将已经成为连通块的部分标为1,连通块外围标为2,其余为0,每次更改只用检查标2的位置是否存在于当前讨论数字相同的,有便更新连通块。

由于初始化时需要标一次连通块,搜索时也需要标连通块,所以使用递归的方式标,不用宽搜

#include<cstdio> 
#include<iostream> 
#include<map> 
#include<cstring> 
using namespace std; 
typedef int int_[9][9]; 

struct fy 
{     
    int a,b; 
    fy(int a,int b) {this->a=a,this->b=b;}  
}; 

int_ a,v,b; 
const int x[]={0,0,1,-1},y[]={1,-1,0,0}; 
int n,tot=0; 
//......................................................... 
int va;

void mark_(int x1,int y1)
{
    v[x1][y1]=1;
    int xx,yy;
    for(int k=0;k<4;k++)
    {
        xx=x1+x[k],yy=y1+y[k];
        if(b[xx][yy]&&v[xx][yy]!=1)
        {
            if(a[xx][yy]==va) mark_(xx,yy);
            else v[xx][yy]=2;
        } 
    }
}
//......................................................... 
bool mar[8];

int h() 
{  
    memset(mar,0,sizeof(mar));
    int ans=0; 
    for(int i=1,j;i<=n;i++) 
     for(j=1;j<=n;j++) 
     { 
         if(v[i][j]!=1&&!mar[a[i][j]]) 
         { 
             mar[a[i][j]]=true; 
             ans++; 
         } 
     } 
    return ans; 
} 
//......................................................... 
int pa(int c) 
{ 
    int xx,yy; 
    bool flag=false; 
    for(int i=1,j,k;i<=n;i++) 
     for(j=1;j<=n;j++) 
     { 
         if(v[i][j]==2&&a[i][j]==c) 
         { 
             flag=true; 
             va=c,mark_(i,j);
         } 
     } 
    return flag;
} 
//......................................................... 
int d; 

bool ida(int now) 
{ 
    int hh=h(); 
    if(now+hh>d) return false; 
    if(hh==0) return true; 
    int_ cp; 
    for(int i=1;i<=tot;i++) 
    { 
        memcpy(cp,v,sizeof(v)); 
        if(!pa(i)) continue; 
        if(ida(now+1)) return true; 
        memcpy(v,cp,sizeof(v)); 
    } 
    return false; 
} 

//......................................................... 

int main() 
{ 
while(true) 
{ 
    scanf("%d",&n); 
    if(n==0) return 0; 
    map<int,int> ma; 
    memset(b,0,sizeof(b));
    memset(v,0,sizeof(v));
    tot=0;
    for(int i=1,j;i<=n;i++) 
     for(j=1;j<=n;j++) 
     { 
         b[i][j]=1; 
         scanf("%d",&a[i][j]); 
         a[i][j]= ma.find(a[i][j])==ma.end() ? ma[a[i][j]]=++tot : ma[a[i][j]]; 
     } 
    v[1][1]=1;
    va=a[1][1],mark_(1,1);
    for(d=0;;d++) 
    { 
        if(ida(0))  
        { 
            printf("%d\n",d); 
            break; 
        } 
    } 
} 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值