hdu 4784 Dinner Coming Soon(spfa+优先队列优化)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4784

题目大意:现在给你N个点,M条有向边,你从点1出发,要到达点N。每条边上有需要花费的时间和钱,你出门时带着R块钱,每个点还可以买一包盐,卖一包盐或者什么都不做,手上最多能拿B包盐,而且你手上有一个机器,能花费1分钟而且不花钱带你从k时空穿越到下一个平行时空 (k+1)%K,平行时空里的点和边和最初空间(0空间)的一样,只是这些点盐的价格会不同。你要在不超过T的时间内从1到达N,并且使钱最大。在途中钱不能为负,点1和点N只能在0空间的时候访问,而且一到点N就马上停了。

解题思路:

       这一看,就想到DP嘛,而且状态和转换都比较简单,这一算时间复杂度,好像也行,就写了个记忆化,果然不出意料,TLE了,试着用最短路优化剪剪枝,还是TLE。。

       后来看了人家解题报告,原来要根据时间,用优先队列优化。话说,这一招,以前真没碰到过,好好的学了下,果然是diao啊!如果是dp普通的写法,直接for循环递推或者spfa都行,for写的话,所有状态都罗列了,可能还是spfa比较快一点,但写起来快,代码短。这题如果用 spfa 的话,也是TLE。spfa 的时间复杂度是O(kE),K(K<<V,貌似一般2左右)为点进入队列几次,一般情况下,稀疏图很好,稠密图的话,需要考虑考虑。对于这题,还是TLE,肿么办?想想,能不能让每个点只进入队列一次。

        由于每个状态往后时间都是增加的,如果用按目前为止到达当前状态所花费的时间递增排序,一旦某个状态从队列里出来,那么它就不可能再次进入队列,其他队列里的状态时间都比它大呀,不可能来更新!这样就可以做到每个状态只进入队列一次,时间复杂度O(VlogV+E),V = N*B*K*T,E = B*K*T*M。

       dp[ pos ][ b ][ k ][ t ],表示在pos点,手上有b包盐,当前在k维空间,已用时t分钟能够获得的最多的钱。另外要注意,一到 N 点就停,所以如果碰到 N 点出来,要continue,否则 WA,因为,试想,某个点只能从 N 点绕过去,并且收益很高,那么如果不处理,肯定过走卖,但这是不行的!

代码如下:

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

const int MAXN = 111;

int N,M,B,K,R,T;

struct Edge
{
    int t,time,fee;
    Edge(int _t,int _time,int _fee)
    {
        t = _t,time = _time,fee = _fee;
    }
};

vector <Edge> G[MAXN];

int dp[MAXN][11][11][222];
int pri[11][MAXN];

struct Node
{
    int pos,b,k,time;
    Node(int _pos,int _b,int _k,int _time)
    {
        pos = _pos,b = _b;
        k = _k,time = _time;
    }
    bool operator < (const Node &tmp) const
    {
        return time > tmp.time;
    }
};

bool vis[MAXN][11][11][222];

void DP()
{
    memset(vis,0,sizeof(vis));
    memset(dp,-1,sizeof(dp));
    priority_queue <Node> q;
    dp[0][0][0][0] = R;
    q.push(Node(0,0,0,0));
    while(!q.empty())
    {
        Node cur = q.top();q.pop();
        if(cur.pos == N-1) continue;//注意,一定要加,否则WA
        for(int i = 0;i < G[cur.pos].size();i++)
        {
            int to = G[cur.pos][i].t;
            int time = G[cur.pos][i].time;
            int fee = G[cur.pos][i].fee;
            for(int salt = -1;salt <= 1;salt++)
            {
                if(cur.b+salt < 0 || cur.b+salt > B) continue;
                if(pri[cur.k][to] == -1 && salt != 0) continue;
                if(cur.time+time > T) continue;
                if((to == 0 || to == N-1) && cur.k != 0) continue;
                int tmp = dp[cur.pos][cur.b][cur.k][cur.time]-salt*pri[cur.k][to]-fee;
                if(tmp < 0) continue;

                if(dp[to][cur.b+salt][cur.k][cur.time+time] < tmp)
                {
                    dp[to][cur.b+salt][cur.k][cur.time+time] = tmp;
                    if(!vis[to][cur.b+salt][cur.k][cur.time+time])
                    {
                        q.push(Node(to,cur.b+salt,cur.k,cur.time+time));
                        vis[to][cur.b+salt][cur.k][cur.time+time] = 1;
                    }
                }
            }
        }

        for(int salt = -1;salt <= 1;salt++)
        {
            if(cur.b+salt < 0 || cur.b+salt > B) continue;
            if(pri[(cur.k+1)%K][cur.pos] == -1 && salt != 0) continue;
            if(cur.time+1 > T) continue;
            if(cur.pos == 0 || cur.pos == N-1) continue;
            int tmp = dp[cur.pos][cur.b][cur.k][cur.time]-salt*pri[(cur.k+1)%K][cur.pos];
            if(tmp < 0) continue;
            if(dp[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] < tmp)
            {
                dp[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] = tmp;
                if(!vis[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1])
                {
                    q.push(Node(cur.pos,cur.b+salt,(cur.k+1)%K,cur.time+1));
                    vis[cur.pos][cur.b+salt][(cur.k+1)%K][cur.time+1] = 1;
                }
            }
        }
    }
}

int main()
{
    int cas = 0;
    int _;
    scanf("%d",&_);
    while(_--)
    {
        scanf("%d%d%d%d%d%d",&N,&M,&B,&K,&R,&T);
        for(int i = 0;i < K;i++)
            for(int j = 0;j < N;j++)
                scanf("%d",&pri[i][j]);
        for(int i = 0;i < N;i++) G[i].clear();
        for(int i = 0;i < M;i++)
        {
            int a,b,c,d;
            scanf("%d%d%d%d",&a,&b,&c,&d);
            a--,b--;
            G[a].push_back(Edge(b,c,d));
        }
        DP();
        int ans = -1;
        for(int i = 0;i <= T;i++)
            ans = max(ans,dp[N-1][0][0][i]);
        printf("Case #%d: ",++cas);
        if(ans < 0) puts("Forever Alone");
        else printf("%d\n",ans);
    }
    return 0;
}

/*
2
3 2 1 2 10 6
-1 1 -1
-1 5 -1
1 2 1 0
2 3 1 1
2 2 1 2 5 5
-1 -1
-1 -1
1 2 10 2
1 2 2 10
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值