【ybtoj高效进阶 21174】景区旅行(二分)(倍增)(状压DP)(DP)

景区旅行

题目链接: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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBTOJ_GXJJ_21174.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(26)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示