hdu 5040 Instrusive【BFS+优先队列】

2014北京网络赛09题,hdu 5040


这次网络赛真是惨,也怪做题策略没想好,当时切完签到题之类的水题之后,马上就去看06青蛙那题去了。结果被那只死青蛙给坑惨了T_T。。。搞了四小时没搞出来...跪给那只青蛙了。。。本来当时是准备要做这道题的,题目描述也是好蛋疼,有人说这题不如直接去看Clarification,不看题目了,这也说明这题题目描述确实不清晰,虽然没这么夸张,题目还是得看的。


重新看这道题,不是很难,最短时间的话,那当然想到BFS,纯BFS的话,只是最短步数,所以我们需要+优先队列。状态的话,只需要坐标(x,y)还有时间cur_t%4这三个元素就行了。


解释一下为什么是cur_t%4,仔细想想会发现,这一题每个状态之间的不同点,除了当前位置的坐标,还有所有监视器的转向,而这个转向是以4为周期的没错吧,所以,只要某两个状态的坐标相同,且t%4也相同的话,那么这就是一个状态,压进队列一次就不用再压第二次了。除了这一点,其他应该没什么难点。后来写了一份代码,一炮就过了。。T_T为什么当时没先做这个?


代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define N 511
using namespace std;
int n,dir[4][2]={{0,-1},{-1,0},{0,1},{1,0}};//E==0,S==1,W==2,N==3
int change[110];
bool vis[N][N][5];
char a[N][N];
struct node
{
    int x,y,t;
    node(){;}   //构造函数
    node(int xx,int yy,int tt){x=xx,y=yy,t=tt;}//构造函数
    bool operator<(const node s)const{return t>s.t;}
};
int check(int x,int y,int t)//return 1代表该位置当前被灯照到,2代表当前位置没有灯照射,也可以走,0代表不可达
{
    if(x<1||x>n||y<1||y>n||a[x][y]=='#') return 0;
    if(change[a[x][y]]!=-1) return 1;
    for(int i=0;i<4;i++)//要知道当前点有没有被照到,需要查看当前点四个方向上有没有灯朝向这个点
    {
        int xx=x+dir[i][0],yy=y+dir[i][1];
        if(xx<1||xx>n||yy<1||yy>n) continue;
        int tmp=change[a[xx][yy]];//tmp>=0代表当前点有灯
        if(tmp>=0&&(tmp+t)%4==i) return 1;
        //(tmp+t)代表当前时间,%4代表此时灯的转向,稍微设计了一下方向函数dir,使得(tmp+t)==i的时候,说明该点被照到
    }
    return 2;
}
int bfs(int sx,int sy)
{
    memset(vis,0,sizeof(vis));
    priority_queue<node> que;
    que.push(node(sx,sy,0));
    while(!que.empty())
    {
        node tmp=que.top();que.pop();
        if(a[tmp.x][tmp.y]=='T') return tmp.t;
        int cur_t;
        //不移动的情况
        cur_t=tmp.t+1;
        if(!vis[tmp.x][tmp.y][cur_t%4]) vis[tmp.x][tmp.y][cur_t%4]=1,que.push(node(tmp.x,tmp.y,cur_t));
        int flag=check(tmp.x,tmp.y,tmp.t);//flag=1说明当前点被照亮
        for(int i=0;i<4;i++)
        {
            int cx=tmp.x+dir[i][0],cy=tmp.y+dir[i][1];
            int che=check(cx,cy,tmp.t);
            if(che==0) continue;
            if(che==1||flag==1) cur_t=tmp.t+3;//要移动的点或者当前点被照亮
            else if(che==2) cur_t=tmp.t+1;
            if(!vis[cx][cy][cur_t%4])
                vis[cx][cy][cur_t%4]=1,que.push(node(cx,cy,cur_t));
        }
    }
    return -1;
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    memset(change,-1,sizeof(change));
    change['E']=0,change['S']=1,change['W']=2,change['N']=3;
    int T,cas=1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%s",a[i]+1);
        int sx,sy;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            if(a[i][j]=='M') sx=i,sy=j;
        int ans=bfs(sx,sy);
        printf("Case #%d: %d\n",cas++,ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于HDU4546问题,还可以使用优先队列(Priority Queue)来解决。以下是使用优先队列的解法思路: 1. 首先,将数组a进行排序,以便后续处理。 2. 创建一个优先队列(最小堆),用于存储组合之和的候选值。 3. 初始化优先队列,将初始情况(即前0个数的组合之和)加入队列。 4. 开始从1到n遍历数组a的元素,对于每个元素a[i],将当前队列中的所有候选值取出,分别加上a[i],然后再将加和的结果作为新的候选值加入队列。 5. 重复步骤4直到遍历完所有元素。 6. 当队列的大小超过k时,将队列中的最小值弹出。 7. 最后,队列中的所有候选值之和即为前k小的组合之和。 以下是使用优先队列解决HDU4546问题的代码示例: ```cpp #include <iostream> #include <vector> #include <queue> #include <functional> using namespace std; int main() { int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a.begin(), a.end()); // 对数组a进行排序 priority_queue<long long, vector<long long>, greater<long long>> pq; // 最小堆 pq.push(0); // 初始情况,前0个数的组合之和为0 for (int i = 0; i < n; i++) { long long num = pq.top(); // 取出当前队列中的最小值 pq.pop(); for (int j = i + 1; j <= n; j++) { pq.push(num + a[i]); // 将所有加和结果作为新的候选值加入队列 num += a[i]; } if (pq.size() > k) { pq.pop(); // 当队列大小超过k时,弹出最小值 } } long long sum = 0; while (!pq.empty()) { sum += pq.top(); // 求队列中所有候选值之和 pq.pop(); } cout << sum << endl; return 0; } ``` 使用优先队列的方法可以有效地找到前k小的组合之和,时间复杂度为O(nklog(k))。希望这个解法对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值