JZOJ 5907. 【NOIP2018模拟10.16】轻功 动态规划

轻功

时间限制: 1 Sec 内存限制: 512 MB

题目描述

题目背景: 尊者神高达进入了基三的世界,作为一个 mmorpg 做任务是必不可少的,然而跑地图却令人十分不爽。好在基三可以使用轻功,但是尊者神高达有些手残,他决定用梅花桩练习轻功。 题目描述: 一共有 n 个木桩,要求从起点(0)开始,经过所有梅花桩,恰好到达终点 n,尊者神高达一共会 k 种门派的轻功,不同门派的轻功经过的梅花桩数不同,花费时间也不同。但是尊者神高达一次只能使用一种轻功,当他使用别的门派的轻功时,需要花费 W 秒切换(开始时可以是任意门派,不需要更换时间)。由于尊者神高达手残,所以经过某些梅花桩(包括起点和终点)时他不能使用一些门派的轻功。尊者神高达想知道他最快多久能到达终点如果无解则输出-1。

输入

第一行 n,k,W 接下来 k 行,每行为 ai 和 wi 代表第 i 种轻功花费 vi 秒经过 ai 个木桩。 接下来一行 Q 为限制条件数量。 接下来 Q 行,每行为 xi 和 ki 代表第 xi 个梅花桩不能使用第 ki 种门派的轻功经过。

输出

一行答案即所需最短时间。

样例输入

6 2 5
1 1
3 10
2
1 1
2 1

样例输出

18

提示

样例解释 1: 先用第二种轻功花费 10 秒到 3,再用 5 秒切换到第一种轻功,最后再用 3 秒时间到 6.一共花费 10+5+3=18 秒
0%的数据 n<=20,k<=10,Q<=200;
对于另外 20%的数据 W=0
对于另外 20%的数据 Q=0
所以数据满足 n<=500,k<=100,Q<=50000,vi<=1e7;
保证数据合法

Hint

Q:请问第一题可不可以往回跳
A:不可以

正解:动态规划O(nk^2)

前言:这道题太水了,最开始我的转移方程错了还能得80pts,至于我在考试中只有20pts,主要是循环没有放对位置,方程错了一点
  1. 我们设f[n][k]为当前位置为n,由k这个技能所转移过来的所需最小时间,我们很快就可以发现可以枚举每个技能
  2. 那么我们的状态转移方程就可以列出来了,当i=j时,我们仍然用上一个技能(k为所在地,a数组记录长度,b数组记录时间)f[k][j]=min(f[k][j],f[k-a[j]][j]+b[j])
  3. 当i!=j时,我们上一个技能是i,这次选用j这个技能,状态转移方程为:f[k][j]=min(f[k][j],f[k-a[j][i]+b[j]+W)(W为技能冷却时间)
  4. 如何判断当前可以使用j这个技能,我们用vector来存每个技能所不能走的地方,然后如果选用j这个技能,那么我们枚举这个技能的每个不能走的点,如果在范围之内就跳过
  5. 如何初始化? 把f[0][i]这些值赋值为0,其他值都为一个极大值,因为在最开始不管从哪个技能开始都不需要时间
  6. 考虑优化,可以加个快读,加个register,自己手打一个min,库里的要比手打的慢
#include<cstdio>
#include<vector>
#define inf 1e17
#define re register int
using namespace std;
int n,K,W,Q,a[505],b[505];
vector<int> p[105];
long long f[505][105],ans=inf;
inline int read() {
    int x=0,cf=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-') cf=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return x*cf;
}
inline long long Min(long long x,long long y) {
    return x<y?x:y;
}
inline bool check(int l,int r,int k) {//枚举每个不能走的点
    for(re i=0;i<p[k].size();i++) {
        if(p[k][i]>=l&&p[k][i]<=r) return false;
    }
    return true;
}
int main() {
    n=read(),K=read(),W=read();
    for(re i=1;i<=K;i++) {
        a[i]=read(),b[i]=read();
    }
    Q=read();
    for(re i=1;i<=Q;i++) {
        int x,y;
        x=read(),y=read();
        p[y].push_back(x);
    }
    for(re i=1;i<=n;i++) {//注意从1开始初始化
        for(re j=0;j<=K;j++) {
            f[i][j]=inf;
        }
    }
    for(re j=0;j<=n;j++) {//当前地点放在外层循环
        for(re i=1;i<=K;i++) {
            for(re k=1;k<=K;k++) {//check(左端点,右端点,当前技能)
                if(k==i&&(j-a[i]>=0)&&check(j-a[i],j,k)) {
                	//继续使用这个技能,注意j-a[i]要大于0
                    f[j][k]=Min(f[j][k],f[j-a[i]][k]+b[i]);
                }//换个技能
                else if((j-a[k]>=0)&&check(j-a[k],j,k)) {
                    f[j][k]=Min(f[j][k],f[j-a[k]][i]+b[k]+W);
                }
            }
        }
    }
    for(re i=1;i<=K;i++) {
    	//因为从任何一个技能都可能成为最优解
        ans=Min(ans,f[n][i]);
    }
    if(ans>=inf) printf("-1");
    else printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值