洛谷P1070 道路游戏(dp+优先队列优化)
题目链接:传送门
题目大意:
有N条相连的环形道路。在1-M的时间内每条路上都会出现不同数量的金币(j时刻i工厂出现的金币数量为val[i][j])。每条路的起点处都有一个工厂,总共N个。
可以从任意工厂i购买机器人,价格为cost[i]。机器人可以设定为从购买的工厂开始顺时针行走长度为1-P的任意时间,并在这段时间内在路上收集金币。
小新能且只能同时拥有一个机器人,当一个机器人行走结束后小新会立即从任意一个工厂内买一个新的机器人,设定时间,并继续顺时针收集。
问M时间内最多能收集多少金币。
2 ≤ N ≤ 1000, 1 ≤ M ≤ 1000, 1 ≤ P ≤ M,各种金额都为1-100间的整数。
假思路1.0(折叠失败:-D):
//92分O(n3)不看题解只能搓出这种拙劣的算法:
//不想看花里胡哨的思路可以直接跳到思路2.0 (-。=)
因为每个时刻j每个工厂i都可以选择买一个机器人或者不买机器人。
①如果买了就可以用于更新之后的1-P时间;
②如果不买就只能从之前k(1≤k≤P)时间内的买了局面更新过来。
注意:从之前的k时间内更新过来的时候需要依次加上之前1-k时间内机器人经过的路径上的所有金币,这里需要预处理一个前缀和sum。
状态:
f[i][j][2]表示j时刻到第i个工厂的最大金额,第三维表示此时此地有没有买机器人,0表示没买,1表示买了。
初始状态:
memset(f, -INF, sizeof f);
f[i][0][0] = 0;
f[i][0][1] = -cost[i+1];
//cost[i+1]是因为此时此地的金额若用于更新未来的时刻,应从路的终点而不是起点购买机器人,
因为这条路的金币已经被收集了,说明机器人已经走过了这条路到达了这条路经的终点。
如:更新f[2][1][0]的时候会从f[2-1][1-1][1] = f[1][0][1]处更新,这里i=1,但是实际上需要购买i+1 = 2处的机器人。
状态转移方程:
f[i][j][0] = max(f[i][j][0], f[i-k][j-k][1] + j-k时刻在第i-k个工厂处经过k时间到达(j,i)处的累计金币);
f[i][j][1] = max(f[i][j][1], f[L][j][0] - cost[i+1]);
//这里的L遍历了j时刻所有的工厂为找出最大值。cost[i+1]同上。
时间复杂度: O(MN2)
#include <bits/stdc++.h> using namespace std;
const int INF = 0x3f3f;
const int MAX_N = 1e3 + ;
#define ind(x) ((x+N-1)%N+N)%N+1 int N, M, P;
int f[MAX_N][MAX_N][];//0表示不走,1表示走
int cost[MAX_N], val[MAX_N][MAX_N], sum[MAX_N][MAX_N]; inline int cal(int i, int j, int k)
{
return sum[i][j] - sum[ind(i-k)][j-k];
} int main()
{
//92分
cin >> N >> M >> P;
for (int i = ; i <= N; i++)
for (int j = ; j <= M; j++)
scanf("%d", &val[i][j]);
for (int i = ; i <= N; i++)
scanf("%d", cost+i);
for (int j = ; j <= M; j++)
for (int i = ; i <= N; i++)
sum[i][j] = sum[ind(i-)][j-] + val[i][j]; for (int i = ; i <= N; i++)
for (int j = ; j <= M; j++)
f[i][j][] = f[i][j][] = -INF;
for (int j = ; j <= M; j++) {
for (int i = ; i <= N; i++) {
if (j == ) {
f[i][j][] = ;
f[i][j][] = f[i][j][] - cost[ind(i+)];
}
for (int k = ; k <= min(P, j); k++) {
if (j-k < )
break;
f[i][j][] = max(f[i][j][], f[ind(i-k)][j-k][] + cal(i, j, k));
}
}
for (int i = ; i <= N; i++) {
for (int l = ; l <= N; l++) {
f[i][j][] = max(f[i][j][], f[l][j][] - cost[ind(i+)]);
}
}
}
int ans = -INF;
for (int i = ; i <= N; i++)
ans = max(ans, f[i][M][]);
cout << ans << endl; return ;
}
/*
2 2 1
1 2
2 3
1 1
*/
假思路1.1(折叠失败×2 :-D):
//100分O(n3)卡过,在TLE的边缘试探(滑稽)
在思路1.0中的状态转移方程中有:
f[i][j][1] = max(f[i][j][1], f[L][j][0] - cost[i+1]);
我枚举l遍历了所有工厂,然鹅这实际上是没有必要的。我们可以直接把所有工厂的最大值保留下来,而不是一一枚举。
因为无论是未来的时刻从j时刻转移,还是最后统计答案,我们都只会用到j时刻所有工厂中的最大金币数,所以非最大值的保留是没有意义的,第二维,去掉。
去掉了原状态的第二维,我们仔细思考可以发现第三维的0和1也是可以去掉的:
如果未来从j时刻转移了,直接在转移时减掉对应的cost即可,而不需要一一保存减掉cost之后的值。
状态:
f[i]表示i时刻所有工厂中的最大金币数。
初始状态:
显然f[0] = 0;//没开始走就没有钱。
状态转移方程:
f[i] = max(f[i], f[i-k] + i-k时刻在第j-k个工厂处经过k时间到达(i,j)处的累计金币 - cost[j+1-k]);
//这里的累计金币同样需要预处理前缀和。
去掉了两个维度,也免去了遍历L产生的硬性O(n),变成了1-P的不完全O(n),752ms偷渡成功。
时间复杂度: O(MNP)
#include <bits/stdc++.h> using namespace std;
const int INF = 0x3f3f3f;
const int MAX_N = 1e3 + ;
#define ind(x) ((x-1)%N+N)%N+1 int N, M, P;
int val[MAX_N][MAX_N], cost[MAX_N], sum[MAX_N][MAX_N];
int f[MAX_N]; inline int cal(int x, int y, int z)
{
return sum[x][y] - sum[ind(x-z)][y-z];
} int main()
{
//100分O(n^3)卡过
cin >> N >> M >> P;
for (int i = ; i <= N; i++)
for (int j = ; j <= M; j++)
scanf("%d", &val[i][j]);
for (int i = ; i <= N; i++)
scanf("%d", cost+i);
for (int j = ; j <= M; j++)
for (int i = ; i <= N; i++)
sum[i][j] = sum[ind(i-)][j-] + val[i][j];
for (int j = ; j <= M; j++)
f[j] = -INF; f[] = ;
for (int i = ; i <= M; i++) {
for (int j = ; j <= N; j++) {
for (int k = ; k <= P; k++) {
if (i < k)
break;
f[i] = max(f[i], f[i-k] + sum[j][i] - sum[ind(j-k)][i-k] - cost[ind(j-k+)]);
}
}
}
cout << f[M] << endl;
return ;
}
思路2.0(没想到你真的就直接来看正解了(〃>皿<)):
//O(n2logn),题目的数据范围是1000,O(n3)卡过的题就是感觉不爽。于是就有了以下的解法
(敲黑板!!!)
首先我们可以这样理解对机器人步数的设定:在开始行走之后的1-P时间内,我们可以在任意时刻打断它,然后买一个新的机器人。
所以对于时刻i,如果此时的机器人是在i-k时刻买的,则时刻i的累计金币,等于i-k时刻的累计金币,加上机器人从i-k时刻走到i时刻累计得到的金币,减去i-k时刻购买机器人花费的金币。
这里的“机器人从i-k时刻走到i时刻累计得到的金币”,预处理前缀和sum后就可以直接算出。
于是有状态:
f[i]表示i时刻的最大累计金币。
初始状态:
显然f[0] = 0;//没开始走就没有钱。
状态转移方程:
f[i] = max(f[i], f[i-k] + sum[j][i] - sum[j-k][i-k] - cost[j-k+1]);
但是这样转移会有很多重复计算!
如图:
在更新点(3,3)的时候已经计算过了点(1,1)和点(2,2)转移到点(3,3)的值,而这个值仅与起点和终点有关。
我们可以把只与起点有关的值分离出来,就可以免去枚举起点的复杂度了:
①转移之前的状态只与起点有关,分离之;
②转移时的cost只与起点有关,分离之;
③转移时的累计金币是通过前缀和计算的:
sum[j][i] - sum[j-k][i-k],
//这里的sum[j-k][i-k]只取决于起点,分离之。sum[j][i]取决于终点,那就不动。
把这些分离出来的值合起来放到优先队列里面:
对于起点(i, j) 构造一个data[i][j] = f[i] - sum[j][i] - cost[j+1];
那么当从起点(i-k, j-k) 更新到点(i, j)时,f[i] = max(f[i], data[i-k][j-k] + sum[j][i]);
发现对于所有的k(1 ≤ k ≤ P),只需要取出data[i-k][j-k]中的最大值,拿来更新f[i]即可。
将这些所有data装入(对应斜行的)优先队列中,就可以用O(logn)的时间从优先队列里拿出最大值了 ♪(^∀^●)ノ
lei了lei了:新的状态转移方程:
f[i] = max(f[i], max(data) + sum[j][i]);
时间复杂度: O(MNlogN)
#include <bits/stdc++.h> using namespace std;
const int INF = 0x3f3f3f;
const int MAX_N = 1e3 + ;
#define ind(x) ((x-1)%N+N)%N+1 struct Node{
int data, t;
Node(int _d = , int _t = ) : data(_d), t(_t) {}
bool operator < (const Node& x) const {
return data < x.data;
}
}; int N, M, P;
int val[MAX_N][MAX_N], cost[MAX_N], sum[MAX_N][MAX_N];
int f[MAX_N]; inline int cal(int x, int y, int z)
{
return sum[x][y] - sum[ind(x-z)][y-z];
} priority_queue <Node> Q[MAX_N]; int main()
{
cin >> N >> M >> P;
for (int i = ; i <= N; i++)
for (int j = ; j <= M; j++)
scanf("%d", &val[i][j]);
for (int i = ; i <= N; i++)
scanf("%d", cost+i);
for (int j = ; j <= M; j++)
for (int i = ; i <= N; i++)
sum[i][j] = sum[ind(i-)][j-] + val[i][j];
for (int j = ; j <= M; j++)
f[j] = -INF; f[] = ;
for (int i = ; i <= M; i++) {
if (i != )
for (int j = ; j <= N; j++) {
while (!Q[ind(i-j)].empty() && i - Q[ind(i-j)].top().t > P)
Q[ind(i-j)].pop();
Node cur;
if (!Q[ind(i-j)].empty())
cur = Q[ind(i-j)].top();
f[i] = max(f[i], cur.data+sum[j][i]);
}
for (int j = ; j <= N; j++) {
Node cur(f[i]-sum[j][i]-cost[ind(j+)], i);
Q[ind(i-j)].push(cur);
}
}
cout << f[M] << endl;
return ;
}
洛谷P1070 道路游戏(dp+优先队列优化)的更多相关文章
- 洛谷 P1070 道路游戏 DP
P1070 道路游戏 题意: 有一个环,环上有n个工厂,每个工厂可以生产价格为x的零钱收割机器人,每个机器人在购买后可以沿着环最多走p条边,一秒走一条,每条边不同时间上出现的金币是不同的,问如何安排购 ...
- 【题解】洛谷P1070 道路游戏(线性DP)
次元传送门:洛谷P1070 思路 一开始以为要用什么玄学优化 没想到O3就可以过了 我们只需要设f[i]为到时间i时的最多金币 需要倒着推回去 即当前值可以从某个点来 那么状态转移方程为: f[i]= ...
- 洛谷 P1070 道路游戏 解题报告
P1070 道路游戏 题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有\(n\)个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依 ...
- 洛谷P1070 道路游戏
P1070 道路游戏 题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有 n 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依次将 ...
- 洛谷 P1070 道路游戏(noip 2009 普及组 第四题)
题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有 nn个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依次将这 nn个机器人工厂编 ...
- 洛谷 P1070 道路游戏
设为第i秒获得的最大值 表示从当前世界是j,从pos走k步到当前点i的最大价值 注意这里的sum可以利用前面的值逐步累加. 我开始做的时候没有想到这一点单独求,然后就超时了. 同时要注意循环的循序问题 ...
- [luogu]P1070 道路游戏[DP]
[luogu]P1070 道路游戏 题目描述小新正在玩一个简单的电脑游戏.游戏中有一条环形马路,马路上有 n 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针 ...
- 洛谷P4027 [NOI2007]货币兑换(dp 斜率优化 cdq 二分)
题意 题目链接 Sol 解题的关键是看到题目里的提示... 设\(f[i]\)表示到第\(i\)天所持有软妹币的最大数量,显然答案为\(max_{i = 1}^n f[i]\) 转移为\(f_i = ...
- 洛谷 P2197 nim游戏
洛谷 P2197 nim游戏 题目描述 甲,乙两个人玩Nim取石子游戏. nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取 ...
随机推荐
- QAbstractItemView区分单双击
系统不可能知道你这一次单击鼠标是为了双击指令,所以在你第一次按下鼠标时,系统会发出一个WM_XBUTTONDOWN(也就是clicked), 当你第二次单击鼠标时,系统先发送WM_XBUTTONDOW ...
- shell 变量介绍
变量命名规则 变量名必须以字母或下划线开头,名字中间只能由字母,数字和下划线组成,大小写是区分的 变量名的长度不得超过255个字符 变量名在有效的范围内必须是唯一的 在Bash中,变量的默认类型都是字 ...
- VS Code 常用快捷键
VS Code 常用快捷键 1.注释: 单行注释:ctrl+/, 注释后再按取消 取消单行注释:alt+shift+A 注释后再按取消 2.移动行 向上移动一行:alt+up 向下移动一行:alt+d ...
- leetcode 刷题 数组类 Two Sum
---恢复内容开始--- Two Sum Given an array of integers ,find two numbers such that they add up to a specifi ...
- java⑧
1.switch的表达式取值: byte short int char Enum(枚举) jdk1.7版本以上支持 String类型 2. break: 01.代表跳出当前方法体!跳出 ...
- 自己写了一个解析json为table的工具类
还需要完善的包括,css的封装,触发事件,数据的获得处理: <!DOCTYPE html> <html> <head> <meta charset=" ...
- DevExpress ASP.NET Bootstrap Controls v18.2新功能详解(二)
行业领先的.NET界面控件2018年第二次重大更新——DevExpress v18.2日前正式发布,本站将以连载的形式为大家介绍新版本新功能.本文将介绍了DevExpress ASP.NET Boot ...
- angular2的依赖注入
更好阅读体验,请看原文 在读这篇文章之前,你要先了解一下什么是依赖注入,网上关于这个的解释很多,大家可以自行Google. 我们这一篇文章还是以QuickStart项目为基础,从头开始讲解怎么在Ang ...
- 计算图 graph
tensorflow,tensor就是数据,flow就是流,tensorflow就是数据流 tensorflow是一个用计算图的形式来表示计算的编程系统,所有的数据和计算都会被转化成计算图上的一个节点 ...
- sptring boot 修改默认Banner
一.自定义banner 启动Spring Boot项目时,在控制台或日志中会默认显示一个Banner,如图所示: 在我们的项目中更希望使用自己的Banner,这样看起来更帅写,但是这对于程序员来说并不 ...