当前位置: 首页 > news >正文

斜率优化dp

文章目录

    • 任务安排1
    • 任务安排2
    • CF1715E Long Way Home

任务安排1

题目描述

在这里插入图片描述
数据范围
1 ≤ N ≤ 5000 , 0 ≤ S ≤ 50 , 1 ≤ T i , C i ≤ 100 1≤N≤5000,0≤S≤50,1≤Ti,Ci≤100 1N50000S501Ti,Ci100



s u m c [ i ] sumc[i] sumc[i]:花费的前缀和

s u n t [ i ] sunt[i] sunt[i]:时间的前缀和

如果选择在第 i i i个任务处添加一个分组,那么在这里多出来的机器启动时间 S S S会对后面的每一个任务都造成影响,导致多出来的花费值是 S ∗ ( s u m c [ n ] − s u m c [ i ] ) S*(sumc[n] - sumc[i]) S(sumc[n]sumc[i]),为了不影响后面的dp,所以对后面的任务造成的花费直接在 i i i处加上。

定义f[i]表示前i个任务分组的最小花费。

则有 f i = m i n { f j + s u m t i ∗ ( s u m c i − s u m c j ) + S ∗ ( s u m c n − s u m c j )    ∣   0 ≤ j ≤ i − 1   } f_i=min \{ f_j + sumt_i*(sumc_i-sumc_j) + S*(sumc_n-sumc_j) \ \ | \ 0 \le j \le i-1 \ \} fi=min{fj+sumti(sumcisumcj)+S(sumcnsumcj)   0ji1 }

#include <bits/stdc++.h>
using namespace std;

const int N = 5010;

int n, s;
long long dp[N], sumt[N], sumc[N];

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    cin >> n >> s;
    for (int i = 1; i <= n; i ++) {
        cin >> sumt[i] >> sumc[i];
        sumt[i] += sumt[i - 1];
        sumc[i] += sumc[i - 1];
    }
    memset(dp, 0x3f, sizeof dp);
    dp[0] = 0;
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j < i; j ++) {
            dp[i] = min(dp[i], dp[j] + sumt[i] * (sumc[i] - sumc[j]) + s * (sumc[n] - sumc[j]));
        }
    }
    cout << dp[n];
    
    return 0;
}

任务安排2

题意与任务安排1相同,但是数据范围有变。
数据范围: 1 ≤ N ≤ 3 × 1 e 5 , 1 ≤ T i , C i ≤ 512 , 0 ≤ S ≤ 512 1≤N≤3×1e5,1≤Ti,Ci≤512,0≤S≤512 1N3×1e51Ti,Ci5120S512


之前的做法是 O ( n 2 ) O(n^2) O(n2),但是本题 n ≤ 3 e 5 n\le3e5 n3e5,于是需要对上面的转移式子进行优化。

这里要用到的就是斜率优化dp

在求 f i f_i fi时,需要遍历每一个 f j , ( j < i ) f_j,(j\lt i) fj(j<i),我们可以把与i相关的值看成常量,将与j有关的值看成变量。

更具体的,将 f j f_j fj看成 y y y s u m c j sumc_j sumcj看成 x x x

    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j < i; j ++) {
            f[i] = min(f[i], f[j] + sumt[i] * (sumc[i] - sumc[j]) + s * (sumc[n] - sumc[j]));
        }
    }

f i = f j − ( s u m t i + S ) ∗ s u m c j + s u m t i ∗ s u m c i + S ∗ s u m c n f_i=f_j - (sumt_i + S) * sumc_j + sumt_i * sumc_i + S * sumc_n fi=fj(sumti+S)sumcj+sumtisumci+Ssumcn

左右移项得到 f j = ( s u m t i + S ) ∗ s u m c j + f i − s u m t i ∗ s u m c i − S ∗ s u m c n f_j = (sumt_i+S) *sumc_j + f_i - sumt_i * sumc_i - S * sumc_n fj=(sumti+S)sumcj+fisumtisumciSsumcn

可以将其看成直线表达式: y = k x + b y = kx + b y=kx+b的形式。其中斜率 k k k是一个定值。

这个直线表达式上的点 ( x , y ) (x, y) (x,y)取值可以是: ( s u m c 0 , f 0 ) , ( s u m c 1 , f 1 ) , ( s u m c 2 , f 2 ) , . . . , ( s u m c i − 1 , f i − 1 ) (sumc_0, f_0), (sumc_1,f_1), (sumc_2,f_2), ..., (sumc_{i-1}, f_{i-1}) (sumc0,f0),(sumc1,f1),(sumc2,f2),...,(sumci1,fi1)。不同的取值代入上面的式子可以得到不同的 f i f_i fi。我们的目标是使得 f i f_i fi最小,也就是让直线表达式中的截距 b b b最小。

在这里插入图片描述

根据上图,我们需要维护这些点的凸包的下边界,因为对于某一个直线来说,与它距离最近的点一定是在这些点上。


  • 如果该直线的斜率为k,怎么找到与它距离最近的点?

我们将凸包上的相邻两点组成直线的斜率标出来,如上图,可以发现 k 1 < k 2 < k 3 k_1<k_2<k_3 k1<k2<k3

k 1 < k < k 2 k_1<k<k_2 k1<k<k2。于是我们只需要找到第一个斜率大于k的位置的点。

  • 另外,本题还有一些其他性质,由于这两个特殊的性质,我们可以进行均摊 O ( 1 ) O(1) O(1)的转移。

1、斜率 k = s u m t i + S k=sumt_i+S k=sumti+S,因为 t t t S S S都是大于 0 0 0的,所以我们从 1 1 1 n n n f i f_i fi时,其对应询问的直线的斜率是单调递增的。

​ 因此,在查询时,如果队头某些点(斜率小于当前直线斜率)不可能作为答案,那么它在后面时更不可能作为答案,可以直接删除。

2、我们不断新加点 ( s u m c j , f j ) (sumc_j,f_j) (sumcj,fj)时,新加点的横坐标也是单调递增的。因为 c c c是大于 0 0 0的。

​ 这意味着我们新加入的点一定不会被删掉。如果新加的点可以和队列末尾的点组成凸包,那么加进去,否则将末尾的点删掉,直到可以和队列中的点组成凸包。

#include <bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define all(x) begin(x),end(x)
#define debug(x) cout<<#x<<": "<<x<<endl;
using namespace std;
using ll = long long;
const int N = 3e5 + 10;

int n, s, q[N];
ll c[N], t[N], f[N];

int main() {
	cin.tie(0);
	ios::sync_with_stdio(false);
	
	cin >> n >> s;
	for (int i = 1; i <= n; i ++) {
		cin >> t[i] >> c[i];
		t[i] += t[i - 1];
		c[i] += c[i - 1];
	}
	
	int hh = 0, tt = -1;
	q[++ tt] = 0;  // (c[0], f[0])
	for (int i = 1; i <= n; i ++) {
		// 求f[i], 对应直线斜率k=t[i]+s
		// hh < tt 确保队列中有>=2个点,若队头点组成的斜率小于当前斜率,出队
		while (hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]]))	hh ++;
		int j = q[hh];  // 此时最优转移点就是队头的点
		f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
		//将点(c[i], f[i])加入队列,加入前将不符合条件的点出队
		while (hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt]]) >= (f[i] - f[q[tt]]) * (c[q[tt]] - c[q[tt - 1]]))	tt --;
		q[++ tt] = i;
	}
	cout << f[n] << endl;
	
	return 0;
}

CF1715E Long Way Home

题意
在这里插入图片描述
数据范围 1 ≤ n , m ≤ 1 e 5 , 1 ≤ k ≤ 20 , 1 ≤ w ≤ 1 e 9 1 \le n,m \le1e5,1 \le k \le 20,1 \le w \le 1e9 1n,m1e51k201w1e9


我们先用 d i j s t r a dijstra dijstra求出 d i s t i dist_i disti,表示只走道路从 1 1 1 i i i的最小值。

  • 然后考虑只飞一次,能使得 d i s t i dist_i disti如何更新?

另设一个数组 d p dp dp,其中 d p i dp_i dpi表示飞完后从 1 1 1 i i i的最短花费。

对于 d i s t i dist_i disti,我们只需要处理从 1 1 1 i i i最后一次是飞的最短花费。
即其路线是 1 → j → i 1 \to j \to i 1ji,那么 d p i = m i n { d i s t j + ( i − j ) 2    ∣ 1 ≤ j ≤ n   } dp_i=min\{dist_j + (i-j)^2 \ \ | 1\le j \le n \ \} dpi=min{distj+(ij)2  ∣1jn }
然后更新之后我们将所有被更新过的点放入优先队列中在跑一边 d i j s t r a dijstra dijstra
这样就得到了只飞一次的最小花费。
题目要求 k k k次,且 k ≤ 20 k\le20 k20,那么我们循环 k k k次这个过程即可。

  • 还有一个问题是转移方程是 O ( n ) O(n) O(n)的一个转移,求 n n n个合起来就是 O ( n 2 ) O(n^2) O(n2),显然需要优化。

d p i = m i n { d i s t j + ( i − j ) 2    ∣ 1 ≤ j ≤ n   } dp_i=min\{dist_j + (i-j)^2 \ \ | 1\le j \le n \ \} dpi=min{distj+(ij)2  ∣1jn }
可以写成 d i s t j + j 2 = 2 i j + d p i − i 2 dist_j+j^2=2ij+dp_i-i^2 distj+j2=2ij+dpii2
这里将其看成 y = k x + b y=kx+b y=kx+b

其中 Y i = d i s t i + i 2 , X i = i Y_i=dist_i+i^2,X_i=i Yi=disti+i2Xi=i
注意本题也有与任务安排类似的特殊性质:即每个直线的斜率k是单调递增的,新加入的点的横坐标也是单调递增的。
有些题目不满足上面的性质,那么维护时就要使用一些其他的方法。

#include <bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define all(x) begin(x),end(x)
#define debug(x) cout<<#x<<": "<<x<<endl;
using namespace std;
using ll = long long;
const int N = 1e5 + 10;

int n, m, k, q[N];
ll dist[N], dp[N];
vector<pair<int, int>> g[N];
bool vis[N];

priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> Q;

void dijkstra() {
	memset(vis, 0, sizeof vis);
	while (Q.size()) {
		auto [d, u] = Q.top();
		Q.pop();
		if (vis[u])	continue;
		vis[u] = true;
		for (auto &[v, w]: g[u]) {
			if (dist[v] > dist[u] + w) {
				dist[v] = dist[u] + w;
				if (!vis[v])	Q.emplace(dist[v], v);
			}
		}
	}
}

inline ll X(ll i) { return i; }
inline ll Y(ll i) { return dp[i] + i * i; }
inline double slope(ll i, ll j) {  // 由于范围太大,交叉相乘判大小会溢出,只能用除法
	return (double)(Y(j) - Y(i)) / (X(j) - X(i));
}


int main() {
	cin.tie(0);
	ios::sync_with_stdio(false);
	
	cin >> n >> m >> k;
	while (m --) {
		int u, v, w;
		cin >> u >> v >> w;
		g[u].push_back({v, w});
		g[v].push_back({u, w});
	}
	
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	Q.emplace(dist[1], 1);
	dijkstra();
	
	for (int _ = 1; _ <= k; _ ++) {
		for (int i = 1; i <= n; i ++)	dp[i] = dist[i];
		// 维护所有点的凸包
		int hh = 0, tt = -1;
		for (int i = 1; i <= n; i ++) {
			while (hh < tt && slope(q[tt - 1], q[tt]) >= slope(q[tt], i))	tt --;
			q[++ tt] = i;
		}
		// 寻找最优转移点更新dist
		for (int i = 1; i <= n; i ++) {
			while (hh < tt && slope(q[hh], q[hh + 1]) <= 2ll * i)	hh ++;
			int j = q[hh];
			if (dist[i] > dp[j] + (ll)(i - j) * (i - j)) {
				dist[i] = dp[j] + (ll)(i - j) * (i - j);
				Q.emplace(dist[i], i);
			}
		}
		dijkstra();
	}
	
	for (int i = 1; i <= n; i ++)	cout << dist[i] << " \n"[i == n];
	
	return 0;
}

相关文章:

  • 前端开发常用网站整理
  • 直流有刷电机电流采集基于STM32F302R8+X-NUCLEO-IHM07M1
  • 27_GitGitHub
  • 微信公众号在线查题功能系统使用
  • WPS JS宏示例-批量添加链接
  • Java核心——面向对象编程(上)包-继承-多态
  • Ambari自动部署Hadoop集群实战
  • 33.0、C语言——C语言预处理(1) - 翻译环境详解
  • java-php-python-springboot网上订餐系统计算机毕业设计
  • 【VUE项目实战】66、上线-通过node创建Web服务器
  • About 9.25 This Week
  • 三、基本命令
  • MySQL中select ... for update会锁表还是锁行?
  • 计算机毕业设计选题 SSM大学生企业推荐系统(含源码+论文)
  • 【Java设计模式 思想原则重构】设计思想、设计原则、重构总结
  • [译] 怎样写一个基础的编译器
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • Apache的基本使用
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • Docker下部署自己的LNMP工作环境
  • es6
  • Git的一些常用操作
  • k8s 面向应用开发者的基础命令
  • maya建模与骨骼动画快速实现人工鱼
  • PAT A1120
  • python_bomb----数据类型总结
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • Redis学习笔记 - pipline(流水线、管道)
  • SQLServer之索引简介
  • vagrant 添加本地 box 安装 laravel homestead
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 关于springcloud Gateway中的限流
  • 七牛云 DV OV EV SSL 证书上线,限时折扣低至 6.75 折!
  • 一些关于Rust在2019年的思考
  • 赢得Docker挑战最佳实践
  • 转载:[译] 内容加速黑科技趣谈
  • Spring Batch JSON 支持
  • # include “ “ 和 # include < >两者的区别
  • #AngularJS#$sce.trustAsResourceUrl
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (2)nginx 安装、启停
  • (done) 两个矩阵 “相似” 是什么意思?
  • (Note)C++中的继承方式
  • (初研) Sentence-embedding fine-tune notebook
  • (定时器/计数器)中断系统(详解与使用)
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (转)Oracle 9i 数据库设计指引全集(1)
  • (转)visual stdio 书签功能介绍
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .libPaths()设置包加载目录
  • .mysql secret在哪_MYSQL基本操作(上)
  • .net core Swagger 过滤部分Api
  • .NET 解决重复提交问题