「BZOJ 4681」[JSOI2010]旅行

给出 n 个点 m 条边的无向图,可以交换任意两条边的权值 k 次,求 1 结点到 n 结点的最短路。 \((1 \le N \le 50,1\le M \le 150,1\le K \le20)\)

Luogu

BZOJ

分析

一开始想忽略每条边的权值,跑一遍最短路,求出边数最少的路径,然后找出这条路径上的边,将这些边从大到小排序,将前 k 大的边与图中所有的边中最小的交换,结果辛辛苦苦调了好久,爆零惹 QAQ 。

然后看题解。

DP + 最短路

把所有的边从小到大排序,那么贪心的做的话,肯定有一个分界线 L ,使得 L 前面的边全部被使用,后面的边都不会被选用,我们枚举这个分界线 L ,设 f[i][j][k] 表示当前是 i 结点,使用了前 L 条边的 j 条,用了 k 次魔法。

于是 DP 时出现了两种情况:

对于当前权值为 w 的边 (u, v)

  1. 这条边是前 L 条边中的一条

f[v][j + 1][k] = min(f[v][j + 1][k], f[u][j][k] + w)

因为这条边和第 j + 1 条边一定会被选用,为了方便枚举,我们从小到大选用。

  1. 这条边不是前 L 条边中的一条

    • f[v][j][k] = min(f[v][j][k], f[u][j][k] + w) 直接使用这条边

    • f[v][j + 1][k + 1] = min(f[v][j + 1][k + 1], f[u][j][k] + w[j + 1]) 将这条边与第 j + 1 条边交换

代码

#include <cmath>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 55
#define M 155
#define mp make_pair
#define INF 0x3f3f3f3f
#define DEBUG puts("ok")
#define tie0 cin.tie(0),cout.tie(0)
#define fastio ios::sync_with_stdio(false)
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;

template <typename T> inline void read(T &x) {
    T f = 1; x = 0; char c;
    for (c = getchar(); !isdigit(c); c = getchar()) if (c == '-') f = -1;
    for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
    x *= f;
}

struct edge {
    int d[2], val;
    friend bool operator < (edge x, edge y) { return x.val < y.val; }
} e[M];

struct node { int u, j, k; };

int n, m, k, ans;
int f[N][M][N];
bool inq[N][M][N];

queue <node> q;
vector <int> ve[N];

void solve(int L) {
    memset(f, INF, sizeof f);
    f[1][0][0] = 0;
    q.push((node){1, 0, 0});
    while (!q.empty()) {
		int u = q.front().u, j = q.front().j, kk = q.front().k;
		q.pop(); inq[u][j][kk] = 0;
		for (int i = ve[u].size() - 1; i >= 0; --i) {
		    int now = ve[u][i], v = e[now].d[u == e[now].d[0]];
		    if (now <= L) {
				if (j < L && f[v][j + 1][kk] > f[u][j][kk] + e[j + 1].val) {
				    f[v][j + 1][kk] = f[u][j][kk] + e[j + 1].val;
				    if (!inq[v][j + 1][kk]) {
						q.push((node){v, j + 1, kk});
						inq[v][j + 1][kk] = 1;
				    }
				}
	    	}
	    	else {
				if (j < L && kk < k && f[v][j + 1][kk + 1] > f[u][j][kk] + e[j + 1].val) {
				    f[v][j + 1][kk + 1] = f[u][j][kk] + e[j + 1].val;
				    if (!inq[v][j + 1][kk + 1]) {
						q.push((node){v, j + 1, kk + 1});
						inq[v][j + 1][kk + 1] = 1;
		    		}
				}
				if (f[v][j][kk] > f[u][j][kk] + e[now].val) {
				    f[v][j][kk] = f[u][j][kk] + e[now].val;
				    if (!inq[v][j][kk]) {
					    q.push((node){v, j, kk});
					    inq[v][j][kk] = 1;
			        }
			    }
		    }
		}
    }
    for (int i = 0; i <= k; ++i) ans = min(ans, f[n][L][i]);
}

int main() {
    ans = 2147483647;
    read(n), read(m), read(k);
    for (int i = 1; i <= m; ++i) read(e[i].d[0]), read(e[i].d[1]), read(e[i].val);
    sort(e + 1, e + 1 + m);
    for (int i = 1; i <= m; ++i) ve[e[i].d[0]].push_back(i), ve[e[i].d[1]].push_back(i);
    for (int i = 0; i <= m; ++i) solve(i);
    printf("%d", ans);
    return 0;
}

顺便挂一下我的 0pts 代码

Code

posted @ 2020-02-02 15:41  小蒟蒻hlw  阅读(106)  评论(0)    收藏  举报