题目链接:传送门

题目大意:

  有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+优先队列优化)的更多相关文章

  1. 洛谷 P1070 道路游戏 DP

    P1070 道路游戏 题意: 有一个环,环上有n个工厂,每个工厂可以生产价格为x的零钱收割机器人,每个机器人在购买后可以沿着环最多走p条边,一秒走一条,每条边不同时间上出现的金币是不同的,问如何安排购 ...

  2. 【题解】洛谷P1070 道路游戏(线性DP)

    次元传送门:洛谷P1070 思路 一开始以为要用什么玄学优化 没想到O3就可以过了 我们只需要设f[i]为到时间i时的最多金币 需要倒着推回去 即当前值可以从某个点来 那么状态转移方程为: f[i]= ...

  3. 洛谷 P1070 道路游戏 解题报告

    P1070 道路游戏 题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有\(n\)个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依 ...

  4. 洛谷P1070 道路游戏

    P1070 道路游戏 题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有 n 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依次将 ...

  5. 洛谷 P1070 道路游戏(noip 2009 普及组 第四题)

    题目描述 小新正在玩一个简单的电脑游戏. 游戏中有一条环形马路,马路上有 nn个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针顺序依次将这 nn个机器人工厂编 ...

  6. 洛谷 P1070 道路游戏

    设为第i秒获得的最大值 表示从当前世界是j,从pos走k步到当前点i的最大价值 注意这里的sum可以利用前面的值逐步累加. 我开始做的时候没有想到这一点单独求,然后就超时了. 同时要注意循环的循序问题 ...

  7. [luogu]P1070 道路游戏[DP]

    [luogu]P1070 道路游戏 题目描述小新正在玩一个简单的电脑游戏.游戏中有一条环形马路,马路上有 n 个机器人工厂,两个相邻机器人工厂之间由一小段马路连接.小新以某个机器人工厂为起点,按顺时针 ...

  8. 洛谷P4027 [NOI2007]货币兑换(dp 斜率优化 cdq 二分)

    题意 题目链接 Sol 解题的关键是看到题目里的提示... 设\(f[i]\)表示到第\(i\)天所持有软妹币的最大数量,显然答案为\(max_{i = 1}^n f[i]\) 转移为\(f_i = ...

  9. 洛谷 P2197 nim游戏

    洛谷 P2197 nim游戏 题目描述 甲,乙两个人玩Nim取石子游戏. nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取 ...

随机推荐

  1. python-flask基础

    get请求: 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用get请求. 传参:get请求传参是放在url中,并且是通过’?’的形式来指定key和value的. post ...

  2. 将数组,矩阵存入csv文件中

    我们在做各种模型训练时,往往会先将数据处理成矩阵,然后交给建模的人去训练.这时通常数据清洗者提交的是保存了矩阵的文件,一般为TXT或csv,接下来主要讲解我在实现这个过程中遇到的一些问题. impor ...

  3. python 自然语言处理(四)____词典资源

    词典或者词典资源是一个词和/或短语及其相关信息的集合,例如:词性和词意定义等相关信息.词典资源附属于文本,而且通常在文本的基础上创建和丰富.下面列举几种nltk中的词典资源. 1. 词汇列表语料库 n ...

  4. 【Oracle安装卸载】oracle卸载

    Oracle卸载比较麻烦,不能简单卸载就完成了,有时没有卸载完整,下次安装不能很好的安装: 当然Oracle卸载也没有那么难,只是步骤比较多.Oracle10g还是Oracle11g卸载步骤都是一样的 ...

  5. UVALive 3401 - Colored Cubes 旋转 难度: 1

    题目 https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_pr ...

  6. centos6.5+python2.7+flask+apache+mod-wsgi部署

    flask部署,使用的是centos6.5,python2.7,版本很重要.基本步骤如下: 一.创建虚拟环境,创建目录把项目拷进去 二.安装mod-wsgi和apache easy_install m ...

  7. Unity3D中的射线与碰撞检测代码

    两种不同写法的射线检测 1.获取鼠标点击的物体 if (Input.GetMouseButtonDown(0)) { Ray ray = MainCamera.ScreenPointToRay(Inp ...

  8. Mysql数据库操作语句总结

    简单复习下: 增insert into -- 删 delete from  -- 改 update table名字 set -- 查 select * from  -- 一.SQL定义 SQL(Str ...

  9. (C/C++学习笔记) 二十二. 标准模板库

    二十二. 标准模板库 ● STL基本介绍 标准模板库(STL, standard template library): C++提供的大量的函数模板(通用算法)和类模板. ※ 为什么我们一般不需要自己写 ...

  10. SQL-21 查找所有员工自入职以来的薪水涨幅情况,给出员工编号emp_no以及其对应的薪水涨幅growth,并按照growth进行升序

    题目描述 查找所有员工自入职以来的薪水涨幅情况,给出员工编号emp_no以及其对应的薪水涨幅growth,并按照growth进行升序CREATE TABLE `employees` (`emp_no` ...