轻功
时间限制: 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,主要是循环没有放对位置,方程错了一点
- 我们设f[n][k]为当前位置为n,由k这个技能所转移过来的所需最小时间,我们很快就可以发现可以枚举每个技能
- 那么我们的状态转移方程就可以列出来了,当i=j时,我们仍然用上一个技能(k为所在地,a数组记录长度,b数组记录时间)f[k][j]=min(f[k][j],f[k-a[j]][j]+b[j])
- 当i!=j时,我们上一个技能是i,这次选用j这个技能,状态转移方程为:f[k][j]=min(f[k][j],f[k-a[j][i]+b[j]+W)(W为技能冷却时间)
- 如何判断当前可以使用j这个技能,我们用vector来存每个技能所不能走的地方,然后如果选用j这个技能,那么我们枚举这个技能的每个不能走的点,如果在范围之内就跳过
- 如何初始化? 把f[0][i]这些值赋值为0,其他值都为一个极大值,因为在最开始不管从哪个技能开始都不需要时间
- 考虑优化,可以加个快读,加个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;
}