传送门
// 题目背景就不说了, 主要说说进行转化后的问题. 就是给定n个点和m条单向边和起点, 边上有权值和时间(时间可以为负) , q次询问终点, 问是否可以恰好在t的时间之内到达终点 , 如果可以那么输出最小花费, 不行则输出No.
// 思路: 首先就是建图, 那么我们考虑一种特殊情况就是在t的时间比较大, 那么它可能会在一个环上绕几圈再到达终点, 所以我们对于一个起点预处理出在[-100, 100]内可以到达的点的最小花费, 因为时间为负, 所以我们加上一个100, 又因为时间的区间问题, 所以预处理不会有太多的情况, 那么我们直接跑最短路就行啦, 直到所有情况处理完毕. 注意由于它可能会经过同一个点多次, 所以不能标记点.
注意: 对于这种一个状态可能会多次枚举的情况都有一个关键性剪枝, 比如这里对于一个点可能会多次入队, 假设dis[n][t], 表示到达n点时间为t的最小花费, 那么对于我们每次推出的点我们要判断是否当前这个状态已经不能得到最优解了, 如果不能这个状态我们是不用再进行扩展的, 也就是如果u.c > dis[u.to][u.t] 那么我们直接将这个状态丢弃就行啦…. 很多类似的题都要有这个剪枝就跑得很快了~~~
AC Code
const int maxn = 1e4+5;
int n, m;
int sx, sy, ex, ey, tt;
int st, ed;
int cnt, head[maxn];
struct node
{
int to, next, w, t;
bool operator < (const node& a) const {
return w > a.w;
}
} e[maxn*6];
void add(int u, int v, int w, int t) {
e[cnt] = (node){v,head[u],w, t};
head[u] = cnt++;
}
void init() {
cnt = 0;
memset(head, -1, sizeof(head));
}
int dis[maxn][205];
void dij(int st)
{
priority_queue<node> q;
for (int i = 1 ; i <= n*m ; i ++) {
Fill(dis[i], inf);
}
dis[st][100] = 0;
q.push((node){st, 0, 0, 100});
while (!q.empty()) {
node u = q.top();
q.pop();
if (u.w > dis[u.to][u.t]) continue; //关键性剪枝
// 如我上面提到的.
for (int i = head[u.to]; ~i; i = e[i].next) {
node k = e[i];
int sumw = u.w + k.w;
int sumt = u.t + k.t;
if (sumt < 0 || sumt > 200 ) continue;
if (sumw < dis[k.to][sumt]) {
dis[k.to][sumt] = sumw;
q.push(node{k.to, 0, sumw, sumt});
}
}
}
}
void solve()
{
init();
cin >> n >> m >> sx >> sy;
st = (sx-1)*m + sy;
int p ; cin >> p ;
for (int i = 1 ; i <= p ; i ++) {
int x1, y1, x2, y2, cost, tim;
cin >> x1 >> y1 >> x2 >> y2 >> cost >> tim;
int id1 = (x1-1)*m+y1;
int id2 = (x2-1)*m+y2;
add(id1, id2, cost, tim);
}
dij(st);
int q; cin >> q;
while(q--) {
cin >> ex >> ey >> tt;
ed = (ex-1)*m+ey;
if (dis[ed][tt+100] == inf) cout << "No" << endl;
else cout << dis[ed][tt+100] << endl;
}
}