洛谷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),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取 ...
随机推荐
- python http 请求 响应 post表单提交
1. 查看请求 响应情况 print(response.text) print(response.headers) print(response.request.body) print(respons ...
- Guidelines for Writing a Good NIPS Paper
By the NIPS 2006 Program Committee With input from Andrew Ng, Peter Dayan, Daphne Koller, Sebastian ...
- 遍历所有子物体中renderer(渲染器)中的material(材质)
//得到所有可渲染的子物体Renderer[] rds = transform.GetComponentsInChildren<Renderer>();//逐一遍历他的子物体中的Rende ...
- 【原创】<Debug> QString
[问题1] 'class QString' has no member named 'toAscii' [解答] 把toAscii().data()改成toLatin1().data() 如果QStr ...
- react全家桶-路由
/src目录下新增一个pages目录,用于存放渲染页面的组件 在/src/pages中新增一个Add.js文件 /src 下的index.js 路由:import React from 'react' ...
- List在遍历中删除t元素
法一:使用普通for循环遍历 注意: 1.从头开始循环,每次删除后 i 减一. 2.从尾开始循环. public class Main { public static voi ...
- spring有关jar包的作用
参考的是spring官网spring4.3版本. 链接:https://docs.spring.io/spring/docs/4.3.19.RELEASE/spring-framework-refer ...
- log4j的参数配置(转)
转载:log4j.properties文件各参数含义与配置 以下是配置文件log4j.properties的一些属性: log4j.rootLogger=WARN, stdout, Rlog4j. ...
- django的FBV和CBV的装饰器例子
备忘 def auth(func): def inner(request,*args,**kwargs): u = request.COOKIES.get('username111') if not ...
- Python如何操作redis
做UI自动化时,遇到一个问题,需要在后台操作完成后,产生结果才能在前端进行操作,但是用自动化在后台操作又很麻烦,就想直接操作数据库,然后再 在前端进行操作:这时遇到一个问题,在后台操作时,会写入到数据 ...