zoukankan      html  css  js  c++  java
  • 斜率优化

    upd: 2021/07/20

    学到一个简单点的方法。
    还是以Print Article 为例。
    给出 (n)(m) 和数列,将 (n) 个数分成任意段,每一段贡献是 (sum^2+m),求最小总贡献
    (f_i = min(f_j + m + {(s_i-s_j)}^2))

    [f_i = min(f_j + m + {(s_i-s_j)}^2)\ f_i = f_j + m + s^2_i + s^2_j -2s_is_j\ f_i - m - s^2_i = -2s_i s_j + f_j + s^2_j\ 令 x = s_j, k = 2 s_i, y = f_j + s^2_j, b = f_i - m -s^2_i\ b = -kx + y\ y = kx + b\ k 从 1sim n一直递增\ 想要使得 b 尽量小,如图,维护凸包,寻找切线\ ]

    WtdZ8S.png


    旧:devinwang的斜率优化入门题单简要题解
    大张旗鼓开了个博客结果博客主要内容是“常规操作,过”TAT
    板子

    int h = 0, t = -1; q[++t] = 0;
    for(int i = 1; i <= n; i++){
    	while(h < t && slope(q[h + 1], q[h]) <= i) h++;
    	f[i] = f[q[h]] + a[i] + i * (s[i] - s[q[h]]);
    	while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    	q[++t] = i; 
    }
    

    Print Article

    题意

    给出 (n)(m) 和数列

    (n) 个数分成任意段,每一段贡献是 (sum^2+m),求最小总贡献

    题解

    (k < j < i) 时,如果 (j) 优于 (k),那么

    [f_j + m + {(s_i-s_j)}^2 le f_k + m + {(s_i - s_k)}^2\ (f_j+s^2_j) - (f_k + s^2_k) le 2(s_j - s_k) *s_i\ frac {(f_j+s^2_j) - (f_k + s^2_k)} {2(s_j - s_k)} le s_i ]

    不妨 (y(i) = f_i + s^2_i, x(i) = 2 s_i)

    (frac {y(j) - y(k)} {x(j) - x(k)} le s_i)

    即 斜率 (k le s_i)

    (g(j, k) = frac {(f_j+s^2_j) - (f_k + s^2_k)} {2(s_j - s_k)})

    (g(i, j) le g(j, k)) 时,

    (g(i, j) le s_i)(i) 优于 (j)(j) 没有存在必要

    (g(i, j) > s_i)(j) 优于 (i) ,同样,(g(j,k) > s_i)(k) 优于 (j)(j) 没有存在必要

    因此,剔除所有 (g(i, j) le g(j, k)),维护一个类似凸包的东西。

    代码

    const int N = 500010;
    int n, m, f[N], s[N], q[N];
    int gety(int j, int k){
    	return f[j] + s[j] * s[j] - f[k] - s[k] * s[k];
    }
    int getx(int j, int k){
    	return 2 * (s[j] - s[k]);
    }
    int main(){
    	while(scanf("%d%d", &n, &m) == 2){
    		for(int i = 1; i <= n; i++)
    			scanf("%d", &s[i]), s[i] += s[i - 1];
    		int h = 0, t = -1; q[++t] = 0;
    		for(int i = 1; i <= n; i++){
    			while(h < t && gety(q[h + 1], q[h]) <= s[i] * getx(q[h + 1], q[h])) h++;
    			f[i] = f[q[h]] + (s[i] - s[q[h]]) * (s[i] - s[q[h]]) + m;
    			while(h < t && gety(i, q[t]) * getx(q[t], q[t - 1]) <= getx(i, q[t]) * gety(q[t], q[t - 1])) t--;
    			q[++t] = i; 
    		}
    		printf("%d
    ", f[n]);
    	}
    	return 0;
    }
    
    

    玩具装箱

    同Print Article,

    [f_j+ {(j + s_j)} ^ 2 - f_k - {(k+ s_k)}^2 < 2(i + s_i - L - 1) * (j + s_k - k - s_k) ]

    const int N = 500010;
    int n, q[N];
    ll f[N], s[N], L;
    ll gety(int j, int k){
    	return f[j] + (j + s[j]) * (j + s[j]) - f[k] - (k + s[k]) * (k + s[k]);
    }
    ll getx(int j, int k){
    	return 2ll * (s[j] + j - s[k] - k);
    }
    int main(){
    	scanf("%d%lld", &n, &L);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &s[i]), s[i] += s[i - 1];
    	int h = 0, t = -1; q[++t] = 0;
    	for(int i = 1; i <= n; i++){
    		while(h < t && gety(q[h + 1], q[h]) <= (i + s[i] - L - 1) * getx(q[h + 1], q[h])) h++;
    		f[i] = f[q[h]] + (i - q[h] + s[i] - s[q[h]] - L - 1) * (i - q[h] + s[i] - s[q[h]] - L - 1);
    		while(h < t && gety(i, q[t]) * getx(q[t], q[t - 1]) <= getx(i, q[t]) * gety(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", f[n]);
    	return 0;
    }
    

    锯木厂选址

    题意

    从山顶上到山底下沿着一条直线种植了 (n) 棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。
    木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。
    你的任务是编写一个程序,读入树的个数和他们的重量与位置,计算最小运输费用。

    懒得翻译(((

    题解

    考虑总的贡献,山顶到山脚从小到大读入,假设 (j < i)

    (d_j) 为到山脚的距离。

    (i) 的时候,最小总贡献

    [f_i = min(sum_{x = 1} ^ j (d_x-d_j)*w_x + sum_{x = j + 1} ^ {i} (d_x - d_i) * w_x + sum_{x = i + 1}^n d_x*w_x) ]

    (s_i)(w_i) 的前缀和

    [f_i = sum_{x = 1}^n d_x*w_x-sum_{x = 1} ^ j d_j*w_x -sum_{x = j + 1} ^ {i} d_i * w_x\ f_i = SUM-d_j*s_j -d_i (s_i - s_j)\ f_i = SUM-max(d_j*s_j +d_i (s_i - s_j))\ ]

    (k < j)(j) 优于 (k)

    [d_j s_j + d_i(s_i - s_j) > d_k * s_k + d_i (s_i - s_k)\ frac {d_js_j-d_ks_k} {s_j - s_k} > d_i ]

    (slope(i, j) > slope(j, k))(j) 无用。

    维护一个下凸包。

    代码

    const int N = 2e5 + 10;
    int n, q[N];
    ll s[N], w[N], d[N], sum;
    ll gety(int j, int k){
    	return d[j] * s[j] - d[k] * s[k];
    }
    ll getx(int j, int k){
    	return s[j] - s[k];
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    ll calc(int j, int i){
    	return sum - d[j] * s[j] - d[i] * (s[i] - s[j]);
    }
    int main(){
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld%lld", &w[i], &d[i]), s[i] = s[i - 1] + w[i];
    	for(int i = n; i >= 1; i--)
    		d[i] += d[i + 1], sum += d[i] * w[i];
    		
    	int h = 0, t = -1; q[++t] = 0;
    	ll ans = 1e18;
    	for(int i = 1; i <= n; i++){
    		while(h < t && slope(q[h + 1], q[h]) > d[i]) h++;
    		ans = min(ans, calc(q[h], i));
    		while(h < t && slope(i, q[t]) > slope(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }
    

    仓库建设

    和前一题差不多吧(((

    const int N = 1e6 + 10;
    int n, q[N];
    ll s[N], w[N], d[N], g[N], f[N], c[N];
    ll gety(int j, int k){
    	return f[j] + g[j] - f[k] - g[k];
    }
    ll getx(int j, int k){
    	return s[j] - s[k];
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld%lld%lld", &d[i], &w[i], &c[i]), 
    		s[i] = s[i - 1] + w[i], g[i] = g[i - 1] + d[i] * w[i]; 
    		
    	int h = 0, t = -1; q[++t] = 0;
    	ll ans = 1e18;
    	for(int i = 1; i <= n; i++){
    		while(h < t && slope(q[h + 1], q[h]) <= d[i]) h++;
    		f[i] = f[q[h]] + c[i] + (s[i] - s[q[h]]) * d[i] - g[i] + g[q[h]]; 
    		while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", f[n]);
    	return 0;
    }
    

    土地购买

    题解

    按照从小到大排序,(a) 第一关键字,(b)第二关键字,去除包含。

    现在 (a) 始终递增,(b) 始终递减。

    显然取连续的一段才是最优的。

    然后常规操作

    代码

    const int N = 1e6 + 10;
    int n, q[N];
    ll f[N];
    struct node{
    	ll a, b;
    	bool operator < (const node x) const {
    		return (a == x.a) ? b < x.b : a < x.a;
    	}
    }po[N];
    ll gety(int j, int k){
    	return f[j] - f[k];
    }
    ll getx(int j, int k){
    	return po[k + 1].b - po[j + 1].b;
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d", &n);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld%lld", &po[i].a, &po[i].b);
    	sort(po + 1, po + n + 1);
    	int cnt = 0;
    	for(int i = 1; i <= n; i++){
    		while(cnt && po[cnt].b <= po[i].b) cnt--;
    		po[++cnt] = po[i];
    	}
    	
    	int h = 0, t = -1; q[++t] = 0;
    	ll ans = 1e18;
    	for(int i = 1; i <= cnt; i++){
    		while(h < t && slope(q[h + 1], q[h]) <= po[i].a) h++;
    		f[i] = f[q[h]] + po[i].a * po[q[h] + 1].b; 
    		while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", f[cnt]);
    	return 0;
    }
    

    特别行动队

    啊这就常规操作

    然后发现常数项最好拖到主函数那里面,否则会炸精度(((

    const int N = 1e6 + 10;
    int n, q[N];
    ll f[N], s[N], a, b, c;
    ll gety(int j, int k){
    	return f[j] + a * s[j] * s[j] - b * s[j] - f[k] - a * s[k] * s[k] + b * s[k];
    }
    ll getx(int j, int k){
    	return s[j] - s[k];
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d", &n);
    	scanf("%lld%lld%lld", &a, &b, &c);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &s[i]), s[i] += s[i - 1];
    	int h = 0, t = -1; q[++t] = 0;
    	for(int i = 1; i <= n; i++){
    		while(h < t && slope(q[h + 1], q[h]) > 2 * a * s[i]) h++;
    		ll x = s[i] - s[q[h]]; f[i] = f[q[h]] + a * x * x + b * x + c; 
    		while(h < t && slope(i, q[t]) > slope(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", f[n]);
    	return 0;
    }
    

    序列分割

    题解

    发现切的顺序对答案无影响

    然后常规操作

    注意到 (s_j = s_k) 时,要特判,否则会除以0 RE

    代码

    const int N = 1e5 + 10;
    int n, k, q[N];
    ll f[N], s[N], g[N], pre[210][N];
    ll gety(int j, int k){
    	return g[j] - s[j] * s[j] - (g[k] - s[k] * s[k]);
    }
    ll getx(int j, int k){
    	return s[k] - s[j];
    }
    double slope(int j, int k){
    	if(s[j] == s[k]) return -1e18;
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d%d", &n, &k);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &s[i]), s[i] += s[i - 1];
    	for(int j = 1; j <= k; j++){
    		int h = 0, t = -1; q[++t] = 0;
    		for(int i = 1; i <= n; i++) g[i] = f[i], f[i] = 0;
    		for(int i = 1; i <= n; i++){
    			while(h < t && slope(q[h + 1], q[h]) <= s[i]) h++;
    			f[i] = g[q[h]] + s[q[h]] * (s[i] - s[q[h]]); pre[j][i] = q[h];
    		//	cout<<i<<"*"<<q[h]<<endl; 
    			while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    			q[++t] = i; 
    		}
    	}
    	printf("%lld
    ", f[n]);
    	int j = k, i = n;
    	while(j) {
    		printf("%d ", pre[j][i]);
    		i = pre[j][i], j--;
    	}
    	return 0;
    }
    

    「SDOI2016」征途

    题解

    一通推柿子答案为

    [min(msum x^2 - {(sum x)}^2) ]

    一通常规操作

    注意代码中注释的那行,0是显然错误的

    代码

    const int N = 3010 + 10;
    int n, q[N];
    ll f[N], g[N], s[N] ,m;
    ll gety(int j, int k){
    	return g[j] + s[j] * s[j] - (g[k] + s[k] * s[k]);
    }
    ll getx(int j, int k){
    	return s[j] - s[k];
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d%lld", &n, &m);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &s[i]), s[i] += s[i - 1], f[i] = s[i] * s[i];
    	for(int j = 1; j < m; j++){
    		int h = 0, t = -1; q[++t] = j;//q[++t] = 0;
    		for(int i = 1; i <= n; i++) g[i] = f[i], f[i] = 0;
    		for(int i = j + 1; i <= n; i++){
    			while(h < t && slope(q[h + 1], q[h]) <= 2 * s[i]) h++;
    			ll x = s[i] - s[q[h]]; f[i] = g[q[h]] + x * x;
    			while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    			q[++t] = i; 
    		}
    	}
    	printf("%lld
    ", - s[n] * s[n] + m * f[n]);
    	return 0;
    }
    

    小P的牧场

    常规操作。。。

    const int N = 1e6 + 10;
    int n, q[N];
    ll a[N], b[N], f[N], s[N], sum;
    ll gety(int j, int k){
    	return f[j] - f[k];
    }
    ll getx(int j, int k){
    	return s[j] - s[k];
    }
    double slope(int j, int k){
    	return 1.0 * gety(j, k) / getx(j, k);
    }
    int main(){
    	scanf("%d%", &n);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &a[i]);
    	for(int i = 1; i <= n; i++)
    		scanf("%lld", &b[i]),
    		s[i] = s[i - 1] + b[i], sum += i * b[i];
    	int h = 0, t = -1; q[++t] = 0;
    	for(int i = 1; i <= n; i++){
    		while(h < t && slope(q[h + 1], q[h]) <= i) h++;
    		f[i] = f[q[h]] + a[i] + i * (s[i] - s[q[h]]);
    		while(h < t && slope(i, q[t]) <= slope(q[t], q[t - 1])) t--;
    		q[++t] = i; 
    	}
    	printf("%lld
    ", f[n] - sum);
    	return 0;
    }
    /*
    4
    2 4 2 4
    3 1 4 2
    */
    
    qaqaq
  • 相关阅读:
    多步操作产生错误,请检查每一步的状态值
    MediaPlayer 播放百度歌曲
    MusicPlayer
    wpf slider 控件模板
    c# 静态构造函数
    好吧,学了久c#,params都不知道怎么用,记录下
    C# 使用各种API
    WPF转义字符
    WPF 执行完一段动画后再关闭窗口
    WPF xml的绑定
  • 原文地址:https://www.cnblogs.com/zdsrs060330/p/14804625.html
Copyright © 2011-2022 走看看