HDU3481题解

HDU3681题解

题意

给定机器人初始位置与一个迷宫矩阵,迷宫中有五种格子”SFGDY”分别代表空,起点,充电点(将电池充满,使用一次后失效),陷阱,开关。机器人要从起点k开始,开始时电池处于满电状态。关掉所有开关,求完成此操作所需要的(最小的(电池最大电量))。

疏解

这道题首先想到的思路是利用bfs进行状压dp,至于为什么想到了用bfs,是因为bfs 天生满足dp的单调递推要求,在前面放入队列中的状态一定是先于后面的状态出现的。写了一个纯粹的bfs,模拟机器人移动,分五种情况考虑格子,朴素的代码是这样的。

queue<node>q;
    q.push((node){sx,sy,0,0,0});
    while(!q.empty())
    {
        //在一般情况下我们说bfs可以解决循环搜索问题是因为bfs一定在最短路上搜索到终点并退出
        node temnode=q.front();
        q.pop();
        int tn=temnode.temn;
        int tm=temnode.temm;
        int ts=temnode.s;
        int tp=temnode.P;
        int tc=temnode.cost;
        for(int i=1;i<=4;i++)
        {
            int nn=tn+mx[i];int nm=tm+my[i];
            if(!illeagal(nn,nm))
            {
                continue;
            }
            if(a[nn][nm]=='S')
            {
            if(tc>=ans)
            {
                continue;
            }
                q.push((node){nn,nm,ts,tp+1,max(tp+1,tc)});
            }
            if(a[nn][nm]=='F')
            {
            if(tc>=ans)
            {
                continue;
            }
                q.push((node){nn,nm,ts,tp+1,max(tp+1,tc)});
            }
            if(a[nn][nm]>=('0'+1)&&a[nn][nm]<=('0'+Gnum))
            {
                if((ts&(1<<((a[nn][nm]-'0')-1)))==0)
                {
                    if(tc>=ans)
                    {
                        continue;
                    }
                    q.push((node){nn,nm,ts|1<<(a[nn][nm]-'0'-1),0,max(tp+1,tc)});
                }
                else
                {
            if(tc>=ans)
              {
                   continue;
                }
                    q.push((node){nn,nm,ts,tp+1,max(tp+1,tc)});
                }
            }
            if(a[nn][nm]=='D')
            {
               continue;
            }
            if(a[nn][nm]>=('0'+Gnum+1)&&a[nn][nm]<=('0'+num))
            {
                if(legal(ts|1<<(a[nn][nm]-'0'-1)))
                {
                    ans=min(ans,max(tp+1,tc));
                }
                if(tc>=ans)
                {
                    continue;
                }
                q.push((node){nn,nm,ts|1<<(a[nn][nm]-'0'-1),tp+1,max(tp+1,tc)});
            }
        }
    }

这里注意,我是先预处理了所有的G点然后是Y点出来并且得到了他们的编号,G点在前Y点在后,方便进行状态压缩。其他方面基本上就是最朴素的bfs,bfs对象是记录有机器人位置,此时疲劳度,路径最大疲劳度与状态的结构体。这样写看上去没什么问题,实际上对于小数据也能过,但是在大数据(大于10)的情况下会mle。这是因为我们的bfs+dp 不能像一般bfs那样查找到一个最短路就退出 所导致的,如果他不能在一个保证了的相对少时间内退出,我们就不得不用已经搜索到的答案做剪枝(如果这个状态下的路径最大疲劳度已经大于已经搜索到的答案就跳过状态),但是这个剪枝实际上作用并不大,我们的队列内存是4的指数次级别的,并且这道题对内存其实是有着很严格的要求,于是这种朴素的bfs证明是不可行的。
紧接着我想到了把所有有效的点(G与Y)做离散化,仅仅保留他们之间相互移动所需的距离,也就是说把这样的一个矩阵化成了类似图结构,把点化作完全的编号对应,并且我发现这样一来那些有效点也就只能vis一次,如果第二次经过与普通的点(S)并没有什么区别,在这种思考下,我得出了这样的代码。

    queue<node>q;
    q.push((node){sx,sy,0,0,0});
   // printf("B %d %d\n",sx,sy);
    while(!q.empty())
    {
        //printf("ss");
        //在一般情况下我们说bfs可以解决循环搜索问题是因为bfs一定在最短路上搜索到终点并退出
        node temnode=q.front();
        q.pop();
        int tn=temnode.temn;
        int tm=temnode.temm;
        int ts=temnode.s;
        int tp=temnode.P;
        int tc=temnode.cost;
        if(tc>=ans)
            {
                continue;
            }
        for(int i=1;i<=Gcounts[tn][tm];i++)
        {

            if((ts&(1<<(Gmaps[tn][tm][i]-1)))!=0)
               {
                   continue;
               }
                    q.push((node){Glocation[tn][tm][i][0],Glocation[tn][tm][i][1],ts|(1<<(Gmaps[tn][tm][i]-1)),0,max(tp+Gdis[tn][tm][i],tc)});

        }
        for(int i=1;i<=Ycounts[tn][tm];i++)
        {
             if((ts&(1<<(Ymaps[tn][tm][i]-1)))!=0)
               {
                   continue;
               }
            if(legal(ts|1<<(Ymaps[tn][tm][i]-1)))
            {
                    ans=min(ans,max(tp+Ydis[tn][tm][i],tc));
                }
            if(tc>=ans)
                {
                    continue;
                }
                q.push((node){Ylocation[tn][tm][i][0],Ylocation[tn][tm][i][1],ts|(1<<(Ymaps[tn][tm][i]-1)),tp+Ydis[tn][tm][i],max(tp+Ydis[tn][tm][i],tc)});
        }
    }

这种思路也是可行的,可惜,还是mle,问题仍然是那个问题,我们没有解决bfs的终点,只是通过剪枝来做了限定。
不过根据每个点只能访问一次这个要求,我这时才发觉这道题其实和vijos1456区别也并不大,哈密顿回路问题,只是这道题中多了疲劳度这个状态而已,于是我想到了将疲劳度作为dp的一维状态记录,而dp储存的是到达这个状态的(最小(路径上最大疲劳)),我得出了这样的代码:

void solve()
{
    //printf("%d\n",start);
    memset(dp,0x3f,sizeof(dp));
    dp[1<<start][start][0]=0;
    for(int i=0;i<(1<<cnt);i++)
    {
        for(int j=0;j<cnt;j++)
        {
            for(int p=0;p<=n*m;p++)
            {
                if((i&(1<<j))==0)continue;
                if(dp[i][j][p]==INF)continue;
                if((i&FinalState)==FinalState)
                {
                        ans=min(ans,dp[i][j][p]);
                }//到达目标状态
                for(int k=0;k<cnt;k++)
                {
                    if((i&(1<<k) )!=0)continue;
                    int tmp=dis[node[j].x][node[j].y][node[k].x][node[k].y];
                    if(tmp==-1)continue;
                    if(str[node[k].x][node[k].y]=='G')dp[i|(1<<k)][k][0]=min(dp[i|(1<<k)][k][0],max(dp[i][j][p],p+tmp));
                    if(str[node[k].x][node[k].y]=='Y')dp[i|(1<<k)][k][p+tmp]=min(dp[i|(1<<k)][k][p+tmp],max(dp[i][j][p],p+tmp));
            }
        }
        }
}
}

很可惜,依然mle,因为dp数组开得太大了(2^15*15*15*15)。
到此为止,我自己已经觉得无计可施了,下面是别人题解中的解决方案。
1.二分答案
二分答案的核心代码是这样的

        while(l<=r){  
            int mid=(l+r)>>1;  
            if(solve(mid)){  
                ans=mid;  
                r=mid-1;  
            }  
            else  
                l=mid+1;  
        }  
bool solve(int mid){  
    int i,j,k;  
    memset(dp,-1,sizeof(dp));  
    dp[1][0]=mid;  
    for(i=0;i<(1<<ids);i++)  
        for(j=0;j<ids;j++){  
            if((i&(1<<j)==0) || dp[i][j]==-1)continue;  
            if((i&fin)==fin)  
                return 1;  
            for(k=0;k<ids;k++){  
                if(i&(1<<k))continue;  
                if(dis[k][j]==-1)continue;  
                if(dp[i][j]>=dis[k][j]){  
                    if(dp[i|(1<<k)][k]==-1 || dp[i|(1<<k)][k]<dp[i][j]-dis[k][j])  
                        dp[i|(1<<k)][k]=dp[i][j]-dis[k][j];  
                    if(map[c[k][0]][c[k][1]]=='G')  
                        dp[i|(1<<k)][k]=mid;  
                }  
            }  
        }  
    return 0;  
}  

枚举答案的思路其实我在agc022_c已经见过了,不过这一次才反映出这种思路的优越之处
1.已经确认答案的视角下,所需要的仅仅是验证是否可行,这样一来就避免在我的思路中一直喋喋不休的对于此时的疲劳度与路径上最大疲劳度的区分,因为我的方法是要找出一个(最小的(路径上最大疲劳度)),因此我不得不把两者都作为有效的数据,但是如果你已知答案,已知路径上最大疲劳度,你所要知道的也就仅仅是此时的疲劳度能否满足恒小于路径上最大疲劳度(也就是电池总电量啦)。
2.避免了对两个疲劳度的区分,也就避免了dp中占用大量空间的此时疲劳度那一维,不论时间还是空间都是要比我的第三个优越的。

完整代码

自己到最后都没有成功写出一个代码,也就不贴了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值