hdu3681

发布时间 2023-09-10 21:34:37作者: Noname_min

一道状态压缩好题

题目大意:

  就是开局有一个起始点和一些必须经过的点,然后从起始点出发,必须要经过所有的必经点,在此基础上求出最小花费,其中引入一个充电池的概念,即到达这个点后花费会清零,但是每个充电点只能经过一次。输出最小花费,不能到达,输出-1。

题目分析:  

  乍一看没有什么思路(搜索就算了,我不知道A*能不能过)。仔细分析,发现搜索显然过不了,观察数据,考虑状态压缩dp。

  但是发现直接dp好像不是很好设计状态,考虑其他方式+状态压缩dp。

 性质:发现不论怎么走,答案都是有界且单调的,更小的值只会更好。由此,可以考虑二分答案和check

 那么该怎么check呢,那么考虑直接用dp来验证,由于套一个log复杂度不会爆炸,所以可以放心使用

 那么该怎么状态压缩呢?

 考虑到空白格子和棋盘和障碍没有实际的意义,也没有用处,反而会扩大范围导致dp范围存疑,由此考虑初始化。

 这时就是套路了,可以用一遍bfs预处理出所有两点间的距离,无法到达就把距离修改为-1

(细节在代码里说明)

代码实现:

 

#include<bits/stdc++.h>

using namespace std;
struct pos
{
    int x,y;
    pos(int x=0,int y=0):x(x),y(y) {}//结构体存坐标
};
struct node
{
    int x,y,s;
    node(int x=0,int y=0,int s=0):x(x),y(y),s(s) {}//重新建图
};
vector<pos>a;//邻接表
int n,m,fid,ac;//ac代表目标状态,fid代表起始点在哪一位
int dir[4][2]= {{0,-1},{-1,0},{0,1},{1,0}};//增量数组,在bfs中使用
char Map[20][20];//存原图
int vis[20][20],d[20][20];//vis和d数组
int dp[(1<<15)+10][16];
void input()
{
    a.clear();
    ac=0;
    for(int i=0; i<n; i++)
    {
        scanf("%s",Map[i]);
        for(int j=0; j<m; j++)
        {
            if(Map[i][j]=='F')
            {
                fid=a.size();//记录起始点在哪一位
                ac+=1<<a.size();//修改目标状态,起始点应该被经过
                a.push_back(pos(i,j));
            }
            if(Map[i][j]=='Y')
            {
                ac+=1<<a.size();//修改状态,y应该被经过
                a.push_back(pos(i,j));
            }
            if(Map[i][j]=='G') a.push_back(pos(i,j));//充电池只要存在就可以使用
        }
    }
}
bool ok(int x,int y)
{
    if(0<=x&&x<n&&0<=y&&y<m&&Map[x][y]!='D'&&vis[x][y]==0) return 1;//可行性检验
    return 0;
}
int bfs(pos a,pos b)
{
    node now=node(a.x,a.y,0),next;
    queue<node>q;
    q.push(now);
    memset(vis,0,sizeof(vis));
    vis[a.x][a.y]=1;
    while(q.size())
    {
        now =q.front();
        q.pop();
        if(now.x==b.x&&now.y==b.y) return now.s;
        for(int i=0; i<4; i++)
        {
            next=now;
            next.x+=dir[i][0];
            next.y+=dir[i][1];
            next.s++;
            if(ok(next.x,next.y)) vis[next.x][next.y]=1,q.push(node(next.x,next.y,next.s));
        }
    }
    return -1;
}
void init()
{
  //预处理距离
for(int i=0; i<a.size(); i++) for(int j=0; j<a.size(); j++) d[i][j]=bfs(a[i],a[j]); } bool DP(int va) {
  //初始化 memset(dp,
-1,sizeof(dp));
  //目标答案 dp[
1<<fid][fid]=va; int len=a.size();//位数 for(int i=1<<fid;i<1<<len;i++)//枚举当前状态 { for(int j=0;j<len;j++) { if(dp[i][j]<0) continue;//如果到不了 if((i&ac)==ac&&dp[i][j]!=-1) return 1;//如果到了目标状态 for(int k=0;k<len;k++) { if(i>>k&1) continue;//若当前“想去”的已经走过了 if(d[j][k]==-1) continue;//如果根本到不了 int tmp=dp[i][j]-d[j][k];//花费减去 if(tmp<0) continue;//当前状态已经没有电了,注意,当tmp=0时正好到达,注意条件 dp[i|(1<<k)][k]=max(dp[i|(1<<k)][k],tmp);//更新答案,因为之前可能已经走过,并且更优 if(Map[a[k].x][a[k].y]=='G') dp[i|(1<<k)][k]=va;//可以充电,回满电量 } } } return 0; } void solve() { int l=0,r=300,ans=1<<30;//答案初始化正无穷,r<=225
  //二分答案
while(l<=r) { int mid=(l+r)>>1; if(DP(mid)) { ans=mid; r=mid-1; } else l=mid+1; } if(ans==(1<<30)) ans=-1; printf("%d\n",ans); } int main() { //freopen("in.txt","r",stdin); while(~scanf("%d%d",&n,&m),n+m) { input(); init(); //debug(); solve(); } return 0; }

 

 

 

这真的是一道好题,综合性比较强,适合狠狠的写过