P2845 [USACO15DEC]Switching on the Lights 开关灯

题目背景

来源:usaco-2015-dec

Farm John 最近新建了一批巨大的牛棚。这些牛棚构成了一个N*N的矩形网络。(1<n<100)

然而bessie十分怕黑,他想计算可以把多少个牛棚的灯打开。

题目描述

有N*N个房间,组成了一张N*N的网格图,Bessie一开始位于左上角(1,1),并且只能上下左右行走。

一开始,只有(1,1)这个房间的灯是亮着的,Bessie只能在亮着灯的房间里活动。

有另外M条信息,每条信息包含四个数a,b,c,d,表示房间(a,b)里有房间(c,d)的灯的开关。

请计算出最多有多少个房间的灯可以被打开

输入输出格式

输入格式:

 

第一行,两个数:N,M(1<m<200000);

第2-m+1行:坐标(x1,y1),(x2,y2)代表房间的坐标(x1,y1)及可以点亮的·房间的坐标(x2,y2);

 

输出格式:

 

一个数,最多可以点亮的房间数

 

输入输出样例

输入样例#1: 复制

3 6
1 1 1 2
2 1 2 2
1 1 1 3
2 3 3 1
1 3 1 2
1 3 2 1

输出样例#1: 复制

5

说明

这里,如果你看得懂英文的话,这里有样例的说明。

Here, Bessie can use the switch in (1,1)to turn on lights in (1,2)and (1,3). She can then walk to (1,3)and turn on the lights in (2,1),from which she can turn on the lights in (2,2). The switch in (2,3)is inaccessible to her, being in an unlit room. She can therefore illuminate at most 5 rooms.

 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define N 102
using namespace std;
int n,m,ans=1,js;
int vis[N][N],vislight[N][N],mark[N][N];
int dis[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
struct Node
{
    int x,y;
};
queue  <Node>  q,q1,q2;
vector <Node> a[N][N];  //
/*void bfs()
{
    int i;
    Node c;
    c.x=1;
    c.y=1;
    q.push(c);
    vis[1][1]=true;   //
    vislight[1][1]=true;     //
    while(!q.empty())  //1112 1213 11不能直达13,所以普通广搜的话,因为11已经出队列,很可能13就变成不可达的,所以要有所完善。
    {                  //
        Node now=q.front();
        q.pop();
        js++;
        if(js==n*n*100)
        return;          //遍历了这么多遍应该灯应该都开了吧。哈哈,骗分真的巧妙
        int xx=now.x,yy=now.y;
        for(i=0;i<a[xx][yy].size();i++) //
        {
            Node g=a[xx][yy][i];
            if(vislight[g.x][g.y]==0)
            ans++;                      //直接开灯的才记数。
            vislight[g.x][g.y]=true;   //先亮灯,然后再标志到过,这步是条件直接开灯的
        }
        for(i=0;i<4;i++)              //这步才是广搜的探索,探索不能直接开灯的地方,但这个地方的灯亮了。
        {                             //才发现这只能前后左右走,不能跳着走
            int xn=now.x+dis[i][0];
			int yn=now.y+dis[i][1];
			if(xn<1||xn>n||yn<1||yn>n||vis[xn][yn]==1||vislight[xn][yn]==0)
			continue;                  //没有到过了和灯亮了才能继续进行。
            vis[xn][yn]=true;
            Node tmp;
            tmp.x=xn;
            tmp.y=yn;
            q.push(tmp);
        }
        q.push(now);  //这步真的骚,还又重新入了队列,多次遍历的第二步关键操作
    }
}*/
//总结:
/*
    1. 从(1,1)开始必然有直接点亮(直接点亮的就计数)
    2. 分两种情况:1.点亮可达 入队列,继续广搜
                   2.没亮可达(隐藏可亮的位置,用新的对列储存)

    3. 在2.1后的操作结束时,显现了当前状态下的所有能亮的点
    4. 再次判断新的可亮的位置是否可达,可达就有新的广搜路径
    5.重复
*/
void bfs()
{
    int i;
    Node c;
    c.x=1;
    c.y=1;
    q.push(c);            //q中记录灯已经打开且能够到达x的房间,常规bfs即可。
    vis[1][1]=true;
    vislight[1][1]=true;
    while(!q.empty())     //1112 1213 11不能直达13,所以普通广搜的话,因为11已经出队列,很可能13就变成不可达的,所以要有所完善。
    {
        Node now=q.front();
        q.pop();          //这里有pop的
        int xx=now.x,yy=now.y;
        for(i=0;i<a[xx][yy].size();i++)
        {
            Node g=a[xx][yy][i];
            if(vislight[g.x][g.y]==0)
            {
                ans++;                      //到一个地方直接开灯的就能直接记数了。
                vislight[g.x][g.y]=true;    //先亮灯,然后再标志到过,这步是条件直接开灯的
            }
        }
        for(i=0;i<4;i++)                    //这步才是广搜的探索,探索不能直接开灯的地方,但这个地方的灯亮了。
        {                                   //才发现这只能前后左右走,不能跳着走
            int xn=now.x+dis[i][0];
			int yn=now.y+dis[i][1];
			if(xn<1||xn>n||yn<1||yn>n)
			continue;                       //没有到过了和灯亮了才能继续进行。
            Node tmp;
            tmp.x=xn;
            tmp.y=yn;
            if(vislight[xn][yn]==0)         //灯没有亮但可达入q1
            {
               if(mark[xn][yn]==0)
               {
                  q1.push(tmp);             //q1中记录灯没有打开但能够到达的房间。
                  mark[xn][yn]=1;           //隐藏可亮的位置的新标记。
               }                            //隐藏的也只用一次就好
            }
            else                            //灯亮可达的情况入q。
            {
                 if(vis[xn][yn]==0)
                 {
                     q.push(tmp);
                     vis[xn][yn]=1;
                 }
            }
         }
      }  //这步把所有广搜路径上的可达与亮灯都开启了。
      while(!q1.empty())   //开始弥补遗漏的情况
      {                    //因为都是可达的,就是灯没有亮。
          Node tmp=q1.front();
          q1.pop();
          if(!vis[tmp.x][tmp.y]&&vislight[tmp.x][tmp.y]) //因为存在灯再次亮了。
          q.push(tmp);      //利用q1将新的已亮可达的点再次入对列,形成新的搜索
          else
          q2.push(tmp);    //不能进队的元素先寄存在q2中,之后再塞回q1。
      }
      while(!q2.empty())
      {
        Node tmp=q2.front();
        q2.pop();
        q1.push(tmp);      //不亮的可达再次回到q1.
      }
      if(!q.empty())      //终止的条件:不在有已亮可达。
      bfs();
}
int main()
{
    int i;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)
    {
        int x,y,nx,ny;
        scanf("%d%d%d%d",&x,&y,&nx,&ny);
        Node t;
        t.x=nx;
        t.y=ny;
        a[x][y].push_back(t);  //就一个二维数组的邻接表
    }
    bfs();
    printf("%d\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值