【题解】Luogu P4962 朋也与光玉

题目传送门!

其实是有点玄学在的

状压 \(DP\)

题意: 每个点一个颜色,找到一条最短的点数为 \(k\) 、恰好经过全部 \(k\) 种颜色的路径。你需要求出这条路径的长度。(\(k <= 13\))

思路: 看数据范围,妥妥的状压。考虑怎么定义状态。

首先,“点数为 \(k\) 、恰好经过全部 \(k\) 种颜色” 说明路径上的每个点颜色必须不同,那直接记录路径已经找到的颜色,再记录一个当前位置,就可以限制下一个点的走向了。

于是定义 \(dp[i][j]\) 为已找到的颜色序列为 \(i\),当前位置为 \(j\) 的最短路径。

\[dp[i|(1 << col[k])][k] = min(dp[i][j] + val, dp[i|(1 << col[k])][k]) \]

然后,就是直接来了。枚举当前状态和位置,枚举下一个可以到达的位置,转移状态即可。

感觉上是一个纯纯的暴力,带点优化。但是不开o2也能过()

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
const int N = 105, M = (1 << 13) + 5;
int n, m, k, col[N];

ll ans = 1e15, dp[M][N];

int tot, head[N], to[N * N], nex[N * N], w[N * N];

void add(int x, int y, int z) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot, w[tot] = z;
}

signed main() {
	memset(head, -1, sizeof(head));
	memset(dp, 0x3f, sizeof(dp));
	scanf("%d %d %d", &n, &m, &k);
	for(int i = 0; i < 1 << k; i ++) {
		for(int j = 1; j <= n; j ++) dp[i][j] = 1e15;
	}
	for(int i = 1; i <= n; i ++) {
		scanf("%d", &col[i]);
		dp[1 << col[i]][i] = 0;
	}
	int u, v, ww;
	for(int i = 1; i <= m; i ++) {
		scanf("%d %d %d", &u, &v, &ww);
		add(u, v, ww);
	}
	for(int i = 0; i < (1 << k); i ++) {
		for(int j = 1; j <= n; j ++) {
			if(!(i & (1 << col[j]))) continue;
			for(int t = head[j]; t != -1; t = nex[t]) {
				if(i & (1 << col[u = to[t]])) continue;
				v = i | (1 << col[u]);
				dp[v][u] = min(dp[v][u], dp[i][j] + w[t]);
			}
		}
	} 
	for(int i = 1; i <= n; i ++) ans = min(ans, dp[(1 << k) - 1][i]);
	if(ans == 1e15) puts("Ushio!");
	else printf("%lld\n", ans);
	return 0;
}

posted @ 2022-07-19 12:10  Spring-Araki  阅读(28)  评论(0编辑  收藏  举报