DP
直接考虑 (dp) 。定义 (dp[i][j]) 表示到 (i) 这个点用 (j) 次优惠的最短路径。
对于 (i) 这个点,只有用与不用优惠两种情况,由此可得状态转移方程:
(其中 (u) 表示上一个点。
int val = min(dp[u][j] + w, dp[u][j + 1]);
dp[u][j] = min(dp[u][j], val);
然后SPFA边跑边进行更新。
但这样会超时,只有 (90) 分(有人玄学Dijk在 (potato) (online) (judge) 上卡过了,但在某谷被 (hack) 了。
代码1
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 10005;
const int INF = 0x3f3f3f3f;
int Min(int x, int y) { return x < y ? x : y; }
struct edge {
int v, c;
edge() {}
edge(int V, int C) {
v = V;
c = C;
}
};
vector<edge> mp[MAXN];
void Add_Edge(int x, int y, int w) {
mp[x].push_back(edge(y, w));
mp[y].push_back(edge(x, w));
return;
}
int dp[MAXN][15];
int n, m, k, s, t;
void read(int &a) {
a = 0;
int k = 1;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
a = (a << 3) + (a << 1) + s - '0';
s = getchar();
}
a *= k;
return;
}
void write(int x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
bool vis[MAXN];
void DP() {
queue<int> q;
q.push(s);
while (!q.empty()) {
int cur = q.front();
q.pop();
vis[cur] = 0;
for (int i = 0; i < mp[cur].size(); i++) {
int v = mp[cur][i].v, w = mp[cur][i].c;
for (int j = 0; j <= k; j++) {
int val = Min(dp[cur][j] + w, dp[cur][j + 1]);
if (val < dp[v][j]) {
dp[v][j] = val;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
}
}
int main() {
memset(dp, 0x3f, sizeof dp);
read(n);
read(m);
read(k);
read(s);
read(t);
for (int i = 1; i <= m; i++) {
int x, y, w;
read(x);
read(y);
read(w);
Add_Edge(x, y, w);
}
for (int i = 0; i <= k; i++) dp[s][i] = 0;
DP();
int mi = INF;
for (int i = 0; i <= k; i++) mi = Min(mi, dp[t][i]);
write(mi);
return 0;
}
Update:
@C202201tanfuwen Dij版过掉了。
每次保存点的时候,把一个点存成 (k) 个点分别保存。
其实就是多建几个点去方便状态转移。(具体看代码吧
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
struct node{
int u;
int w;
int k;
node(){}
node(int U,int W,int K){
u=U,w=W,k=K;
}
};
bool operator < (node a,node b){
return a.w>b.w;
}
const int MAXN=10005;
int dis[MAXN][15];
vector<node> g[MAXN];
priority_queue<node> que;
bool vis[MAXN][15];
int n,m,k;
void dijkstra(int s){
memset(dis,0x3f,sizeof(dis));
dis[s][0]=0;
que.push(node(s,0,0));
while(!que.empty()){
int now=que.top().u;
int now_k=que.top().k;
que.pop();
if(vis[now][now_k]) continue;
vis[now][now_k]=1;
for(int j=0;j<g[now].size();j++){
int v=g[now][j].u;
int w=g[now][j].w;
if(dis[v][now_k]>dis[now][now_k]+w && !vis[v][now_k]){
dis[v][now_k]=dis[now][now_k]+w;
que.push(node(v,dis[v][now_k],now_k));
}
if(now_k+1<=k){
if(dis[v][now_k+1]>dis[now][now_k] && !vis[v][now_k+1]){
dis[v][now_k+1]=dis[now][now_k];
que.push(node(v,dis[v][now_k+1],now_k+1));
}
}
}
}
}
int main(){
scanf("%d %d %d",&n,&m,&k);
int s,t;
scanf("%d %d",&s,&t);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d %d %d",&u,&v,&w);
g[u].push_back(node(v,w,0));
g[v].push_back(node(u,w,0));
}
dijkstra(s);
int minn=0x3f3f3f3f;
for(int j=0;j<=k;j++){
minn=min(minn,dis[t][j]);
}
printf("%d",minn);
return 0;
}
分层图+最短路
分层是个好东西。
我们把原图复制 (k) 层,然后将第一层的点和它在第一层上有连边的点在第二层对应的点连一条边权为0的有向边。
这时,你最多可以跨越 (k) 层,其实就是你最多可以用 (k) 次优惠。
然后你就会惊奇的发现,原题直接变成一道最短路板题。
最后枚举每一层终点的对应点,取最小的答案即可。注意存边最好使用链式前向星,会节省大量空间。
这里用的某谷上大佬的分层实现,把它单独拎出来:
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &x, &y, &w);
addedge(x, y, w);
addedge(y, x, w);
for (int j = 1; j <= k; j++) {
addedge(x + (j * n), y + (j * n), w);
addedge(y + (j * n), x + (j * n), w);
addedge(x + ((j - 1) * n), y + (j * n), 0);
addedge(y + ((j - 1) * n), x + (j * n), 0);
}
}
for (int i = 1; i <= k; i++)
addedge(t + (i - 1) * n, t + i * n, 0);
代码2
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int MAXM = 5000050;
const int MAXN = 1000050;
const int INF = 0x3f3f3f3f;
struct edge {
int to, next, val;
} a[MAXM];
struct node {
int dist, id;
node() {}
node(int D, int I) {
dist = D;
id = I;
}
};
bool operator<(node xi, node yi) { return xi.dist > yi.dist; }
int n, m, k, s, t, cnt = 0, head[MAXN];
int dist[MAXN];
bool vis[MAXN];
void Add_Edge(int x, int y, int c) {
a[cnt].to = y;
a[cnt].next = head[x];
a[cnt].val = c;
head[x] = cnt++;
}
void dijkstra(int s) {
memset(vis, 0, sizeof(vis));
memset(dist, INF, sizeof(dist));
priority_queue<node> q;
q.push(node(0, s));
dist[s] = 0;
while (!q.empty()) {
node cur = q.top();
q.pop();
if (vis[cur.id])
continue;
vis[cur.id] = 1;
for (int i = head[cur.id]; i != -1; i = a[i].next) {
int v = a[i].to;
if (dist[v] > a[i].val + cur.dist) {
dist[v] = a[i].val + cur.dist;
q.push(node(dist[v], v));
}
}
}
}
int main() {
memset(head, -1, sizeof(head));
scanf("%d %d %d", &n, &m, &k);
scanf("%d %d", &s, &t);
for (int i = 1; i <= m; i++) {
int x, y, w;
scanf("%d %d %d", &x, &y, &w);
Add_Edge(x, y, w);
Add_Edge(y, x, w);
for (int j = 1; j <= k; j++) {
Add_Edge(x + (j * n), y + (j * n), w);
Add_Edge(y + (j * n), x + (j * n), w);
Add_Edge(x + ((j - 1) * n), y + (j * n), 0);
Add_Edge(y + ((j - 1) * n), x + (j * n), 0);
}
}
for (int i = 1; i <= k; i++) Add_Edge(t + (i - 1) * n, t + i * n, 0);
dijkstra(s);
printf("%d", dist[t + k * n]);
return 0;
}