1. 程式人生 > 其它 >【ybtoj高效進階 21174】景區旅行(二分)(倍增)(狀壓DP)(DP)

【ybtoj高效進階 21174】景區旅行(二分)(倍增)(狀壓DP)(DP)

給你一個無向圖,邊有貢獻,然後你有一個油量,每走一條邊油量減一,然後總貢獻加上邊的貢獻。 然後你的油量不能是負數,你可以在一些地方加油,你有油量上限,每個地方也有能加到的油量,你的油量會變成這兩個的最小值,然後每個地方加油也有對於的費用。 然後多次詢問,每次告訴你出發點,要的總貢獻和有的錢,然後問你要至少要有那麼多的總貢獻,最多能省下多少錢。 (如果用所有錢都沒有那麼多貢獻就輸出 -1)

景區旅行

題目連結:ybtoj高效進階 21174

題目大意

給你一個無向圖,邊有貢獻,然後你有一個油量,每走一條邊油量減一,然後總貢獻加上邊的貢獻。
然後你的油量不能是負數,你可以在一些地方加油,你有油量上限,每個地方也有能加到的油量,你的油量會變成這兩個的最小值,然後每個地方加油也有對於的費用。
然後多次詢問,每次告訴你出發點,要的總貢獻和有的錢,然後問你要至少要有那麼多的總貢獻,最多能省下多少錢。
(如果用所有錢都沒有那麼多貢獻就輸出 -1)

思路

首先我們考慮不加油,給出初始有的油和起點終點,問你最大貢獻。

這個可以通過倍增加狀壓 DP 之類的玩意兒快速實現。
然後你會發現你就可以通過另外的 DP 求出起點是 \(i\)

,用的錢恰好是 \(j\) 能有的最大貢獻。

然後不難看出搞個這個的字首和,我們就可以二分出最少要用的錢數。
然後就有答案了。

程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

int n, m, C, T, v[101], u[101], x, y;
ll z, go[101][101][21], f[101][10001];
ll ww[101], w[101][101];

int main() {
//	freopen("trip.in", "r", stdin);
//	freopen("trip.out", "w", stdout);
	
	scanf("%d %d %d %d", &n, &m, &C, &T);
	for (int i = 1; i <= n; i++) scanf("%d %d", &v[i], &u[i]);
	
	memset(go, -1, sizeof(go));
	for (int i = 1; i <= n; i++) go[i][i][0] = 0;
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %lld", &x, &y, &z);
		go[x][y][0] = max(go[x][y][0], z);
//		go[y][x][0] = max(go[y][x][0], z);
	}
	
	for (int d = 1; d <= 20; d++)//倍增
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) {
				go[i][j][d] = go[i][j][d - 1];
				for (int k = 1; k <= n; k++)
					if (go[i][k][d - 1] != -1 && go[k][j][d - 1] != -1)
						go[i][j][d] = max(go[i][j][d], go[i][k][d - 1] + go[k][j][d - 1]);
			} 
	memset(w, -1, sizeof(w));
	for (int i = 1; i <= n; i++) {//狀壓 DP
		int noww = min(C, u[i]);
		w[i][i] = 0;
		for (int j = 20; j >= 0; j--)
			if (noww & (1 << j)) {
				noww -= (1 << j);
				for (int k = 1; k <= n; k++) ww[k] = w[i][k];
				for (int k = 1; k <= n; k++)
					for (int l = 1; l <= n; l++)
						if (ww[k] != -1 && go[k][l][j] != -1)
							w[i][l] = max(w[i][l], ww[k] + go[k][l][j]);
	
			}
	}
	
	memset(f, -1, sizeof(f));//普通 DP
	for (int j = 0; j <= n * n; j++)
		for (int i = 1; i <= n; i++) {
			if (j < v[i]) f[i][j] = 0;
				else {
					for (int k = 1; k <= n; k++)
						if (f[k][j - v[i]] != -1 && w[i][k] != -1)
							f[i][j] = max(f[i][j], f[k][j - v[i]] + w[i][k]);
				}
		}
	
	for (int j = 1; j <= n * n; j++)
		for (int i = 1; i <= n; i++) {
			f[i][j] = max(f[i][j], f[i][j - 1]);
		}
	while (T--) {
		scanf("%d %d %lld", &x, &y, &z);
		int l = 0, r = y, ans = -1;
		while (l <= r) {//二分
			int mid = (l + r) >> 1;
			if (f[x][mid] >= z) ans = mid, r = mid - 1;
				else l = mid + 1;
		}
		if (ans == -1) printf("-1\n");
			else printf("%d\n", y - ans);
	}
	
	return 0;
}