FZU1892接水管游戏-BFS加上简单的状态压缩和位运算处理

原题地址:http://acm.fzu.edu.cn/problem.php?pid=1892

Problem 1892 接水管游戏

Accept: 108    Submit: 498

Time Limit: 1000 mSec    Memory Limit : 32768 KB

Problem Description

接水管游戏的规则如下:
1、在N*N大小的方格上有两个特别的水管,分别为进水口和出水口;
2、有7种1*1大小的水管需要放在这N*N大小的方格内,使得水流能够从进水口经过这些水管流到出水口;
3、当水流流到了出水口或者水流在流到出水口之前流出了水管,那么游戏就结束了;
4、游戏的分数为水流经过水管的数目(除了进水口和出水口);
5、游戏中有四个方向,分别表示东南西北,可用数字编号说明水管中水流的方向,1表示可以向东流,2表示可以向南流,4表示可以向西流,8表示可以向北流;
6、七种水管的形状如下: :编号为15;:编号为9;:编号为6;
:编号为3; :编号为12; :编号为10;
:编号为5;
7、进水口的编号需要再加上16,出水口的编号需要再加上32,进水口和出水口中的水流只有一个方向,游戏中有且只有一个进水口和一个出水口;
8、每一时刻,水流都会沿着水管的水流方向流到相邻的方格之中,如果该方格没有水管或者水管接头不对应,那么水流将流出水管。
现在水管已经接好了,请你计算出在当前情况下,能够得到的分数。

Input

输入数据第一行为一整数T,表示有T组输入数据。每组输入数据的第一行为一个整数N(5≤N≤50),表示方格的大小,以下有N行,每行有N个数字Mij(1≤i,j≤N),表示第i行第j列方格中的水管编号,Mij=0表示该方格没有水管。

Output

对于每组数据,输出一行一个整数,表示能够得到的分数。

Sample Input

253 5 5 6 010 3 5 15 610 10 0 10 4010 9 5 12 189 5 5 5 1253 5 5 6 010 0 0 15 610 10 0 0 4010 9 5 12 189 5 5 5 12

Sample Output

1814

Hint

Source

福州大学第七届程序设计竞赛

这题一看就能知道是BFS,而且数据小,不会超时。但是接口的问题怎么处理,这个是重点。

题目很明显,管子就是状态压缩。题目第5点:游戏中有四个方向,分别表示东南西北,可用数字编号说明水管中水流的方向,1表示可以向东流,2表示可以向南流,4表示可以向西流,8表示可以向北流。1,2,4,8就是二进制数的4个位,编号15的管子,就是1+2+4+8=15,说明水管通向东南西北四个方向。同理,编号为9的管子,1+8=9,通向东和北。如此类推。

那么问题来了,分析一个管子通向哪几个方向,要怎么实现?之前学过点状压基础的就能知道,用位运算操作。

位运算:<<,左移 位运算,将数的二进制数向左移n位,左边的舍弃,右边补0 。  1<<n就是将1向左移动n位,左边舍弃,右边补0,其数值上等效于1*(2^n)。比如,1<<3,本来1的二进制数是0001,左移3位后,变为1000,等于8,2^3 。

位运算:&,且 位运算,对两个数的二进制数按位进行且运算,同为1,则该位为1 ,否则为0。如下:

00101

11100

----------------

00100

那么,结合这两个运算就能知道,管子通往哪几个方向。实现代码如下:

for(i=0; i<4; i++)

{

if(  ( n&(1<<i) ) >0 )

{

// 对应的二进制第 i 位就是1,就是这个位代表的方向可以流通

}

}

那么接下来,判断相邻的两个管子能不能接上,也就好办了。就是先判断管子通向哪个方向,然后判断那个方向的相邻管子也可以通向反方向。

左边管子方向朝东,它东边的管子可以通向西,那么它们就连通了。实现代码如下:

// 地图用二维的 int 储存

int map[50][50] ;

// 定义结构体,now,表示当前坐标;

// 用来移动的数组

int xx[4]= {0,1,0,-1};

int yy[4]= {1,0,-1,0};

for(i=0; i<4; i++)

{

if(  (maps[now.x][now.y]&(1<<i)  ) >0  )

{

f(  (maps[ now.x+xx[i] ]  [ now.y+yy[i] ]  )  &  ( 1<<( (2+i)%4) ) ) >0  )

{

//  连通!!(有没有感觉酱紫很燃~2333)

}

}

}

会了这些之后,再用BFS就没问题了~但是位运算让我很头大,细节问题让我调试了很久,比赛时没做出来,当天晚上改到凌晨1点多,但依然没对,还是第二天改对的……

AC代码如下:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;

int maps[50][50];
bool book[50][50];
int xx[4]= {0,1,0,-1};
int yy[4]= {1,0,-1,0};
int t,n;
int x1,x2,y3,y2,i,j;
int flag=0;
int sum=0;
int k=0;
int num=0;

struct node
{
    int x,y,com,step;
};
node now;
node next;
queue <node> qq;

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        {
            num=0;
            // 注意,入口和出口都只有一个方向。减去16或32就是管子的出入口方向
            for(i=0; i<n; i++)
            {
                for(j=0; j<n; j++)
                {
                    scanf("%d",&maps[i][j]);
                    if(maps[i][j]>=32)
                    {
                        maps[i][j]-=32;
                        x2=i;
                        y2=j;
                    }
                    else if(maps[i][j]>=16)
                    {
                        maps[i][j]-=16;
                        x1=i;
                        y3=j;
                    }
                    if(maps[i][j]!=0)
                    {
                        num++;
                    }
                }
            }
            /*
            for(int i=0;i<n;i++)
            {
                for(int j=0;j<n;j++)
                {
                    printf("%d ",maps[i][j]);
                }
                printf("\n");
            }
            // */
            next.x=x1;
            next.y=y3;
            next.step=0;
            flag=0x3f3f3f3f;//大于10^9且在int范围内,挺好用的一个最大值
            sum=0;
            for(i=0; i<4; i++)
            {
                if((maps[x1][y3]&(1<<i))>0)
                {
                    next.com=(i+2)%4;//判断起始点的入水方向(起始点出水方向的反方向),以备后面使用
                    //printf("%d\n",next.com);
                    break;
                }
            }
            qq.push(next);
            memset(book,0,sizeof(book));
            while(!qq.empty())
            {
                now=qq.front();
                qq.pop();
                //printf("$%d\n",k++);
                //判断两步内是否会结束游戏(到终点,或者水流到管子外)
                for(i=0; i<4; i++)
                {
                    if((maps[now.x][now.y]&(1<<i))>0)//找到所有方向
                    //不为终点,不越界,并且能连通
                    if( ((now.x+xx[i])!=x2||(now.y+yy[i])!=y2) && ((now.x+xx[i])>=0) && ((now.x+xx[i])<n) && ((now.y+yy[i])>=0) && ((now.y+yy[i])<n)  && ((maps[now.x+xx[i]][now.y+yy[i]])&( 1<<((2+i))%4) )>0)
                    {
                        for(j=0; j<4; j++)//下一步依旧不为终点,不越界,能连通
                        {
                            if((maps[now.x+xx[i]][now.y+yy[i]]&(1<<j))>0)
                            if( ((now.x+xx[i]+xx[j])!=x2||(now.y+yy[i]+yy[j])!=y2) && ((now.x+xx[i]+xx[j])>=0) && ((now.x+xx[i]+xx[j])<n) && ((now.y+yy[i]+yy[j])>=0) && ((now.y+yy[i]+yy[j])<n)  && ((maps[now.x+xx[i]+xx[j]][now.y+yy[i]+yy[j]])&( 1<<((2+j)%4)) )>0)
                            {

                            }
                            else//否则标记步数,并且步数取最小值
                            {
                                flag=min(flag,now.step+1);
                                //printf("^^%d\n",flag);
                            }
                        }
                    }
                    else//否则标记结束时刻的步数
                    {
                        flag=min(flag,now.step);
                        //printf("^%d\n",flag);
                    }
                }
                for(i=0; i<4; i++)
                {
                    if(((maps[now.x][now.y]&(1<<i))>0)&&(now.com!=i))//除了进水方向的出水方向(水不能从原路返回)
                    {
                        //下一个管子没流到过,不为终点不越界并且可以连通
                        if(!book[now.x+xx[i]][now.y+yy[i]] && ((now.x+xx[i])!=x2||(now.y+yy[i])!=y2) && ((now.x+xx[i])>=0) && ((now.x+xx[i])<n) && ((now.y+yy[i])>=0) && ((now.y+yy[i])<n)  && ((maps[now.x+xx[i]][now.y+yy[i]])&( 1<<((2+i)%4)) )>0 )
                        {
                            //printf("!!%d %d\n",now.x+xx[i],now.y+yy[i]);
                            sum++;//答案++
                            book[now.x+xx[i]][now.y+yy[i]]=1;//标记
                            next.x=now.x+xx[i];
                            next.y=now.y+yy[i];
                            next.com=(i+2)%4;
                            next.step=now.step+1;
                            if(next.step<=flag)//下一步不会到终点,不会越界流出,那么才压入队列。flag在之前已经判断过,为游戏结束的步数
                            {
                                qq.push(next);
                                //printf("%d %d %d\n",now.x+xx[i],now.y+yy[i],now.step);
                                //getchar();
                            }
                        }
                    }
                }
            }
            printf("%d\n",sum);
            while(!qq.empty())
            {
                qq.pop();
            }
        }
    }
    return 0;
}
/*
给你多提供一组数据测试使用~不用谢~我叫红领巾~
Simple input:
4
4
18 15 15 15
9 15 15 15
15 15 15 15
15 15 15 40
5
3 5 5 6 0
10 3 5 15 6
10 10 0 10 40
10 9 5 12 18
9 5 5 5 12
5
3 5 5 6 0
10 0 0 15 6
10 10 0 0 40
10 9 5 12 18
9 5 5 5 12

Simple output:
10
18
14
*/

最后,女神镇宅~

时间: 2024-10-07 22:45:36

FZU1892接水管游戏-BFS加上简单的状态压缩和位运算处理的相关文章

poj2443(简单的状态压缩)

POJ2443 Set Operation Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 2679   Accepted: 1050 Description You are given N sets, the i-th set (represent by S(i)) have C(i) element (Here "set" isn't entirely the same as the "set&qu

cf1051d 简单的状态压缩dp

/* 给定一个二行n列的格子,在里面填黑白色,要求通过黑白色将格子分为k块 请问有多少种填色方式 dp[j][k][0,1,2,3] 填到第j列,有k块,第j列的颜色, */ #include<bits/stdc++.h> using namespace std; #define ll long long #define mod 998244353 //0全白,1黑白,2白黑,3黑黑 ll dp[1050][1050*2][4],n,k; int main(){ cin>>n>

Vijos 1206 CoVH之再破难关 BFS与位运算

1.题意:一个由01组成的4*4的矩阵,可以实现相邻元素交换位置的操作,给出初试状态和目标状态,试求最少操作数的方案: 2.输入输出:输入给出初试矩阵和目标矩阵:要求输出最小操作的次数: 3.分析:输出最小操作数,很容易联想到使用BFS,这里为了方便表示,把4*4的矩阵拉成一个16个数的数组来看,并用一个16位二进制数表示其状态:用位运算来实现交换某两位的状态,另外再稍微注意一下如何在表示"相邻"的概念即可: 1 # include <iostream> 2 # inclu

hdu2209翻纸牌游戏(bfs+状态压缩)

Problem Description 有一种纸牌游戏,很有意思,给你N张纸牌,一字排开,纸牌有正反两面,开始的纸牌可能是一种乱的状态(有些朝正,有些朝反),现在你需要整理这些纸牌.但是麻烦的是,每当你翻一张纸牌(由正翻到反,或者有反翻到正)时,他左右两张纸牌(最左边和最右边的纸牌,只会影响附近一张)也必须跟着翻动,现在给你一个乱的状态,问你能否把他们整理好,使得每张纸牌都正面朝上,如果可以,最少需要多少次操作. Input 有多个case,每个case输入一行01符号串(长度不超过20),1表

建图方式一 之 ”前向星“ BFS&amp;&amp;DFS 简单应用

三种建图方式,邻接矩阵.前向星(边表集).邻接链表! 耗时一晚上 ,好好研究了一下 前向星,因为我的指针用的实在是很烂,所以还是 入赘 前向星吧. 问了学长,看了大牛们的博客,终于有点收获了,个人认为 前向星Very Well. 前向星 建图方法: 以储存边的方式来储存图.在构造图时,把边存放在数组里,不需使用指针,只需一个 next  即可是整个图构建完成 . 适用条件: 点集特别多的稀疏图,边数多且繁杂,开邻接矩阵会浪费大量内存. 时间复杂度: O(m),并不比邻接链表差. #include

24点游戏计算器 (简单四则运算)(c++)

24点游戏计算器 (简单四则运算)(c++):https://github.com/liuxinig/cpp_1001/blob/master/24dian_siZeIN.txt 1 //24点统计 2 3 #include <iostream> 4 #include <cmath> 5 using namespace std; 6 #define N 14 7 //a数组存四个数字 8 int cixu[3],fuHao[3],p[N],sum = 0; 9 float a0[4

org-mode游戏化的简单实现

12 Org Gamification 游戏化是一个很火的概念,Org-mode是一个实现GTD的极好工具,将两者结合起来想必非常有趣. 下面就是一个简单的对Org-mode游戏化的尝试,主要就是通过完成任务赚取积分,用然后用积分购买奖励物品. 1 积分操作 完成任务可以获取到积分,默认情况下[#A]级任务增加30积分,[#B]级任务增加20积分,[#C]级任务增加10积分. 但可以通过为每个entry设置REWARD属性的方式自定义完成该entry所获得的奖励积分数,需要为整数. (defun

如果给游戏都加上进度条,你还能玩上几分钟?

你根本不知道有多少关卡在前面等待,你还没有打通的每一个关卡甚至每一帧画面,总会带来惊喜. 文/张书乐 原载于<人民邮电报>2016年5月13日<乐游记>专栏102期 偶然间打开了在网络上火了几年的脱口秀节目<罗辑思维>,其中一期名为<游戏是个怎样的世界>让我颇有了点兴趣.可节目听到一半,就觉得不对味了. 自诩是个胖子的罗振宇在节目中秉承着一贯颠覆常规认知.从另一个角度切入来启发思维的话语逻辑,告诉听众说,其实在沉迷游戏的原因中,不仅包括"刷&quo

迷宫的最短路径(BFS的简单应用)

[题目简述]:给定一个大小为n*m的迷宫.迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动.请求出起点到终点所需的最小步数.(注:本题假定从起点一定可以移动到终点) 如图: #S######.# ......#..# .#.##.##.# .#........ ##.##.#### ....#....# .#######.# ....#..... .####.###. ....#...G# [分析]:广度优先搜索是由近及远的搜索,所以在这个问题中采用BFS很合适,只要注意访问过的位