hfut 1247 H 技术员BangFu

 这题的确是一道好题,很好的将状态dp以及图论的最短路径,这里上面的权值表示的花费的钱,另外还有很多约束问题,

 首先大体描述下这道题,就是一个技术员,遍历N个节点,首先他一开始在0号节点,且是星期一,然后遍历1..N-1 编号的节点,这里要求每个节点只能走一次,而且必须每个节点都得走到,最后还要回到0号节点,而且从一个定点到另一个顶点是要花费p天的时间,还要要一定的钱,而且在每个节点也至少得待一天的时间,最后的最后,要我们解决的问题是,首先判定他是否能够每个节点都能走到,另外,如果能又是否能在星期六或星期天到,若是,那么我们至少得花多少钱呢!

  问题已经很清晰了,现在的问题就是如何求了,按照我开始的思路,首先判定这是否是一个遍历所有节点的回路,然后既然这是一个状态dp的问题,那么肯定是涉及到状态的改变的,那么状态是什么呢,这里就是走过的节点,可以作为状态,如果已经走过,那么为1,否则为0,那么N个节点可以表示成一个二进制串,然后相对应的十进制值就是状态数,如上,我们如何判断状态的合法性,这里就是走过的节点就不要回去了,所以就很好判断了,也就是说每一位只能置1一次,好,搞过之后,我们在找状态方程,因为这里好像很隐藏,

因为问题描述当中,有要求的天数,和花费的钱数,那么这个方程到底怎么表示呢!

 这里设dp[state][des][d]  这个方程的意思就是状态为state时,且到达了des节点,且此时是星期,另外这个值就是达到这点所花费的钱   0<=state<2^N,1<=des<=N-1,0<=d<7

   然后就是初始化问题,dp[0][0][0]=inf,dp[1][0][0]=0,然后就是用bfs遍历找出求最短路劲了,这里最短路劲也好求,首先在输入的时候,建立邻接表,bfs的用队列维护,这里就如上所述,所涉及的要保存的信息很多,于是我们想到结构体,用结构体来表示节点以及边,这样,这个过程就很好办了,当然我们知道bfs的过程中,还有一个visit的,这里也需要建立类似的标记,这里要和dp有同样的大小,

 这样搞过之后,最后就很好办了,首先dp[2^N-1][1....N][D] ,循环遍历在目标1....N个节点中看看有没到0号节点的,两个fou循环,找出最小值,同时判断是否能够在星期六和星期天道,这些都很好办了,,接下来就可以去是实现了,,就是苦力活了,,有许多的细节,要考虑全面!

  

   下面的代码就是写给自己看的,成功AC,,要实现就自己写吧!

 #include<iostream>

#include<vector>

#include<queue>

#include<algorithm>

#define inf 0x7fffffff

using namespace std; 

struct edge

{

  int y,p,t;  //含义如题所述 

};

struct node

{

  int s,n,d;    //s表示当前状态,n表示节点标号,d表示经过的天数 

};

vector<edge> num[11];

int dp[1<<10][11][7];

int visit[1<<10][11][7];

queue<node> qu;

int N,M;

void sloveinput()

{

  //初始化

  for(int i=0;i<(1<<N);i++)

   for(int j=0;j<N;j++)

    for(int k=0;k<7;k++)

      {

        dp[i][j][k]=inf;

        visit[i][j][k]=0;

      }

  for(int i=0;i<N;i++)

   num[i].clear();

  edge tmp;

  for(int i=0;i<M;i++)

  {

    int a;

    cin>>a>>tmp.y>>tmp.p>>tmp.t;

    num[a].push_back(tmp);

  }

  //邻接表以建立好 

}

void slovedp()

{

  //这个函数就是要求dp[][][]里面能求的值,要用队列 

  node start;

  edge tmp; 

  start.s=1;start.n=0;start.d=0;

  // 

  tmp.y=0;tmp.p=0;tmp.t=0;

  //这里从0号节点开始,00...001 所以状态s1,但是节点y0,当然p,和t

 // qu.clear();

  dp[1][0][0]=0; 

  visit[1][0][0]=1;

  qu.push(start);

  while(!qu.empty())  //开始bfs

  {

    node tp=qu.front();

    qu.pop();

    for(int i=0;i<num[tp.n].size();i++)  //在这些临边遍历 

    {

      if(!(tp.s&(1<<num[tp.n][i].y))) 

       //这说明以前这个节点还是没有走过的,那么可扩展,

       //这里就是判定合法状态的条件,状态dp一般到要这样

       {

          //建立节点保存,并入队列,

          node node1;

          node1.s=tp.s|(1<<num[tp.n][i].y);

          node1.n=num[tp.n][i].y;

          node1.d=(tp.d+num[tp.n][i].t+1)%7;

          //到这里后,如何判定入栈呢

          if(dp[tp.s][tp.n][tp.d]+num[tp.n][i].p<dp[node1.s][node1.n][node1.d])

          //如果通过上一个状态 花费加上到当前节点的花费小于 当前状态的花费,则扩充,入栈,//这里是重要的地方,状态转移方程 

          {

          dp[node1.s][node1.n][node1.d]=dp[tp.s][tp.n][tp.d]+num[tp.n][i].p;  //更新吧 

          if(!visit[node1.s][node1.n][node1.d])

           {

             visit[node1.s][node1.n][node1.d]=1;

             qu.push(node1);

           }

          }

       } 

    } 

  } 

void sloveans()

{

   //这个收尾就很简单喽

   int end=(1<<N)-1;

   int flag1=0,flag2=0;

   int ans=inf; 

   for(int i=1;i<=N-1;i++)  //遍历

    for(int j=0;j<num[i].size();j++)

     {

       if(num[i][j].y==0)  //说明有回到0节点的

        {

           for(int dd=0;dd<7;dd++)  //判定是否能在周六或周日到 

            {

              if(dp[end][i][dd]<inf)  

              //说明有成功到达0节点的,且遍历了所有节点,这里一开始写成里 dp[end][num[i][j].y][dd]<inf,细节问题一定要注意啊 

              {

                flag1=1;    

                //再是第二个条件,看是否能在周六或周日到达 

                if(((dd+num[i][j].t)%7)>=5&&ans>dp[end][i][dd]+num[i][j].p)

                 //这里把星期一去掉,默认从0,那么经过t天后,如果是5,6,就说明了是能在星期六和星期日到达的 

                 //并且找到满足的最小花费值 

                 {

                   flag2=1;

                   ans=dp[end][i][dd]+num[i][j].p; 

                 } 

              } 

            }

        } 

     }

    if(!flag1) cout<<"It's not my thing!"<<endl;  

    else if(!flag2) cout<<"Oh, My god!"<<endl;

    else cout<<ans<<endl;

}

int main()

{

    while(cin>>N>>M)

    {

      sloveinput();

      slovedp();

      sloveans(); 

    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值