代码编织梦想

例题:排版问题

  • 根据题目,我们不难得出朴素的转移方程:

    f i = m i n (   f j + ( ( w j + 1 + . . . w i ) − a ) 2   ) f_i=min(\space f_j+((w_{j+1}+...w_i)-a)^2\space) fi=min( fj+((wj+1+...wi)a)2 )

  • 用前缀和表示为:

    f i = m i n (   f j + ( s u m i − s u m j − a ) 2   ) f_i=min(\space f_j+(sum_i-sum_j-a)^2\space) fi=min( fj+(sumisumja)2 )

由于我们肯定要枚举 i i i ,在枚举 i i i 时, s u m i sum_i sumi 是个定值, 而 a a a 是个常数,因此 s u m i − a sum_i-a sumia 是个定值

  • 我们令 c i = s u m i − a c_i=sum_i-a ci=sumia :

    f i = m i n (   f j + ( c i − s u m j ) 2   ) f_i=min(\space f_j+(c_i-sum_j)^2\space) fi=min( fj+(cisumj)2 )

  • 展开:

    f i = m i n (   f j + c i 2 − 2 ∗ c i ∗ s u m j + s u m j 2   ) f_i=min(\space f_j+c_i^2-2*c_i*sum_j+sum_j^2\space) fi=min( fj+ci22cisumj+sumj2 )

  • 暂且将 min 去掉并移项

    f j + s u m j 2 = 2 ∗ c i ∗ s u m j + f i + c i 2 f_j + sum_j^2=2*c_i*sum_j+f_i+c_i^2 fj+sumj2=2cisumj+fi+ci2

  • s u m j sum_j sumj x   x\space x , 令   f j + s u m j 2 \space f_j + sum_j^2  fj+sumj2 y   y\space y  , 令   2 ∗ c i \space 2*c_i  2ci k   k\space k 

    y = k x + f i + c i 2 y=kx+f_i+c_i^2 y=kx+fi+ci2

形如一个一次函数

我们需要使得 f i f_i fi 最小,那么 f i + c i 2 f_i+c_i^2 fi+ci2 即截距最小 (枚举 i i i 时, c i c_i ci 相当于定值,上文提及)

对于每个不同的 j j j ,其 x , y x,y x,y 不同, 我们记为 x j , y j x_j,y_j xj,yj

对于每个不同的 i i i ,其 k k k 不同, 我们记为 k i k_i ki

问题相当于给定一个一次函数的斜率 k i k_i ki, 给定一些点 ( x j , y j ) (x_j,y_j) (xj,yj) ( 0 ≤ j < i 0\leq j < i 0j<i) ,一次函数需要过其中一点,求这些点中使得这个一次函数截距最小的点 ( x j , y j ) (x_j,y_j) (xj,yj) , 求出 j j j,然后再转移求得 f i f_i fi

  • 如图

在这里插入图片描述

  • 现将直线向上平移,第一个碰到的点即为使得截距最小的点

在这里插入图片描述

  • 可得最终最优的答案一定在一个凸壳(斜率逐渐增大)上
    在这里插入图片描述

  • 如何求出对于斜率为 k i k_i ki 直线第一个碰到的点?

    我们记 k i , j k_{i,j} ki,j 表示 i , j i,j i,j 两点之间的斜率

    • k i < k 1 , 2 k_i<k_{1,2} ki<k1,2 ,那么 1 1 1 号点为第一个碰到的点

    • 在这里插入图片描述

    • k i = k 1 , 2 k_i=k_{1,2} ki=k1,2 ,那么 1 , 2 1,2 1,2 号点都为第一个碰到的点,任选其一转移即可
      在这里插入图片描述

    • k 1 , 2 < k < k 2 , 3 k_{1,2}<k<k_{2,3} k1,2<k<k2,3 ,那么 2 2 2 号点即为第一个碰到的点
      在这里插入图片描述

    • . . . ... ...

    • 可得,对于任意 k i k_i ki ,使得 k j − 1 , j < k i < k j , j + 1 k_{j-1,j}< k_i< k_{j,j+1} kj1,j<ki<kj,j+1 j j j 即为第一个碰到的点(这里小于号取不取等号都可以,如上文第二种情况所述)

  • 求出第一个碰到的点后,我们即可求出 f i f_i fi

  • 对于一个新的点,我们已知其斜率,我们需要:

    • 求出第一个碰到的点
    • 求出新个点的 x , y x,y x,y
    • 将新的点加入图中
  • 由于 x = s u m j x=sum_j x=sumj , k = 2 ∗ c i = 2 ∗ ( s u m i − a ) k=2*c_i=2*(sum_i-a) k=2ci=2(sumia),两者均单调递增

  • 因此记 k i k_i ki 第一个碰到的点为 x p , y p x_p,y_p xp,yp ,那么对于 k i + 1 k_{i+1} ki+1 ,对于 p p p 之前的点肯定不会作为 k i + 1 k_{i+1} ki+1 的第一个碰到的点

  • 具体需要的操作:

    • 对于新来的 k i k_i ki ,求出第一个碰到的点以及 x i , y i x_i,y_i xi,yi,删除两点间斜率小于 k i k_i ki 的点
    • 由于 x x x 单调递增,我们在结尾处插入 x i , y i x_i,y_i xi,yi 同时维护住凸包的性质,即保证两点间斜率逐渐变大
  • 考虑用单调队列维护

//定义,即上文所述
#define k(i) (2*(s[i]-a))
#define y(i) (f[i] + s[i]*s[i])
#define x(i) (s[i])
//单调队列部分
	for(int i=1;i<=n;i++)
    {
        while(head<tail && (q[head] 与 q[head+1] 之间的斜率 < k(i)) )
            head++;
        f[i] = calc(i, q[head]); // 队首即为第一个碰到的点
        while(head<tail && (i 与 q[tail-1] 间斜率 小于等于 i 与 q[tail] 之间的斜率 )
            tail--;
        q[++tail] = i;
    }
判断 (q[head] 与 q[head+1] 之间的斜率 是否小于 k(i))bool cmp1(int p,int q,int i)
{
	return (y(q)-y(p)) < k(i) * (x(q)-x(p));	
}

while(head<tail && cmp1(q[head],q[head+1],i))
		head++;

判断 i 与 q[tail-1] 间斜率 是否小于等于 i 与 q[tail] 之间的斜率:
bool cmp2(int i,int j1,int i2,int j2)
{
    return (y(j1)-y(i1))*(x(j2)-x(i2)) >= (y(j2)-y(i2))*(x(j1)-x(i1));
}

while(head<tail && cmp2(i,q[tail-1],i,q[tail]))
		tail--;
		
#include <iostream>
#define int long long
using namespace std;
const int MAXN=2e5+5;
int n,a,head,tail,s[MAXN],f[MAXN],q[MAXN];
#define k(i) (2*(s[i]-a))
#define y(i) (f[i] + s[i]*s[i])
#define x(i) (s[i])
bool cmp1(int p,int q,int i)
{
	return (y(q)-y(p)) < k(i) * (x(q)-x(p));	
}
bool cmp2(int i1,int j1,int i2,int j2)
{
    return (y(j1)-y(i1))*(x(j2)-x(i2)) >= (y(j2)-y(i2))*(x(j1)-x(i1));
}
int calc(int i,int j)
{
    return f[j]+(s[i]-s[j]-a)*(s[i]-s[j]-a);
}
signed main()
{
    cin>>n>>a;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i]; 
		s[i] += s[i-1];
    }
    for(int i=1;i<=n;i++)
    {
        while(head<tail && cmp1(q[head],q[head+1],i)) head++;
        f[i] = calc(i, q[head]);
        while(head<tail && cmp2(i,q[tail-1],i,q[tail])) tail--;
        q[++tail] = i;
    }
    cout<<f[n]<<endl;
}

例题:任务安排1

推出方程后稍微改写一下即可
在这里插入图片描述

  • 此题 x , k x,k x,k 也均为单调递增
#include <iostream>
#define int long long
using namespace std;
const int MAXN=2e5+5;
int n,s,head,tail,T[MAXN],F[MAXN],g[MAXN],q[MAXN];
#define k(i) (s+T[i])
#define y(i) (g[i])
#define x(i) (F[i])
bool cmp1(int p,int q,int i)
{
	return (y(q)-y(p)) < k(i) * (x(q)-x(p));	
}
bool cmp2(int i1,int j1,int i2,int j2)
{
    return (y(j1)-y(i1))*(x(j2)-x(i2)) >= (y(j2)-y(i2))*(x(j1)-x(i1));
}
int calc(int i,int j)
{
   	return g[j] + T[i]*F[i] - T[i]*F[j] + s*F[n] - s*F[j];
}
signed main()
{
    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        cin>>T[i]>>F[i]; 
		T[i] += T[i-1];
		F[i] += F[i-1];
    }
    for(int i=1;i<=n;i++)
    {
        while(head<tail && cmp1(q[head],q[head+1],i)) head++;
        g[i] = calc(i, q[head]);
        while(head<tail && cmp2(i,q[tail-1],i,q[tail])) tail--;
        q[++tail] = i;
    }
    cout<<g[n]<<endl;
}

例题:任务安排2

在这里插入图片描述

  • 此题和上一题一样但是这里的 T i T_i Ti 可能为负数,因此 k k k 不一定单调递增

  • 这时我们就不能利用单调性在单调队列中弹出斜率比当前 k i k_i ki 小的元素了,因为后面的 k i k_i ki 第一个碰到的点可能在这些元素之中

  • 但由于凸包斜率单调递增的特性,我们可以考虑在队列中二分找出 k j − 1 , j < k i < k j , j + 1 k_{j-1,j}< k_i< k_{j,j+1} kj1,j<ki<kj,j+1 j j j ,即为第一个碰到的点

bool cmp1(int p,int q,int i)
{
	return (y(q)-y(p)) < k(i) * (x(q)-x(p));	
}
// 二分查找 k[j-1,j]<k[i]<k[j,j+1] 的点
int search(int l,int r,int target)
{
	while(l<=r-5)
	{
		int mid=(l+r)>>1;
		if(cmp1(q[mid],q[mid+1],target)) l=mid;
		else r=mid;
	}
	for(int i=l;i<=r;i++)
		if(!cmp1(q[i],q[i+1],target)) return q[i];
}
#include <iostream>
#define int long long
using namespace std;
const int MAXN=3e5+5;
int n,s,head,tail,T[MAXN],F[MAXN],g[MAXN],q[MAXN];
#define k(i) (s+T[i])
#define y(i) (g[i])
#define x(i) (F[i])
bool cmp1(int p,int q,int i)
{
	return (y(q)-y(p)) < k(i) * (x(q)-x(p));	
}
bool cmp2(int i1,int j1,int i2,int j2)
{
    return (y(j1)-y(i1))*(x(j2)-x(i2)) >= (y(j2)-y(i2))*(x(j1)-x(i1));
}
int calc(int i,int j)
{
	return g[j] + T[i]*F[i] - T[i]*F[j] + s*F[n] - s*F[j];
}
int search(int l,int r,int target)
{
	while(l<=r-5)
	{
		int mid=(l+r)>>1;
		if(cmp1(q[mid],q[mid+1],target)) l=mid;
		else r=mid;
	}
	for(int i=l;i<=r;i++)
		if(!cmp1(q[i],q[i+1],target)) return q[i];
}
signed main()
{
    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        cin>>T[i]>>F[i]; 
		T[i] += T[i-1];
		F[i] += F[i-1];
    }
    for(int i=1;i<=n;i++)
    {
        int j = search(head,tail,i);
        g[i] = calc(i, j);
        while(head<tail && cmp2(i,q[tail-1],i,q[tail])) tail--;
        q[++tail] = i;
    }
    cout<<g[n]<<endl;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_73113801/article/details/127998311

bzoj1096(zjoi2007)仓库建设--斜率优化dp_greninja_wu的博客-爱代码爱编程

【链接】 bzoj1096 【解题报告】 定义 sum[i] sum[i]表示前 i i个工厂产品数量的前缀和。 定义p[i]p[i]表示前 i i个工厂的距离乘产品数

学习笔记——斜率优化 dp-爱代码爱编程

引入 斜率优化,是单调队列优化的一个进阶版本,为了更好地理解,先来回顾一下单调队列吧~ 所谓单调队列优化,就是对于形如: d p i

斜率优化dp入门-爱代码爱编程

推荐2篇博客: 斜率优化DP详解-知乎 斜率优化-OI Wiki 例一: P5785 [SDOI2012]任务安排 首先,暴力dp的话,我们可以定义 f [ i

斜率优化dp_blueluelue161的博客-爱代码爱编程

斜率优化 DP,又叫凸壳优化 (Convex Hull Trick) 。它是用来求解 d p

c语言中常见的逻辑错误_一枚小菜程序员的博客-爱代码爱编程

  常见错误一: “=” 和“==”混在一起    int main() { int ret; if(ret = 1) { ...... } return 0; } 结果:变量被错误赋值,逻辑判断错误 错误二: 定义较大的全局变量造成 编译文件过大 int a[2000]

c语言百日千题系列之《忘情水题》第一日_会敲代码的史蒂夫.的博客-爱代码爱编程

目录 绪论 1.最大数位置 2.与指定数字相同的数的个数 3.蓝桥杯2013年第四届真题-核桃的数量 4.求所给范围内水仙花数并排列 5.最大值和最小值的差 6.计算书费 7.角谷猜想 8. 最高的分数 9.年龄与疾病 10.-百钱百鸡问题 绪论       本文是C语言百日千题系列《忘情水题》的第一篇专栏文章,主要为初学

pc_dram_xuchaoxin1375的博客-爱代码爱编程

动态 RAM(DRAM)的刷新 刷新的过程实质上是先将原存信息读出,再由刷新放大器形成原信息并重新写入的再生过程 根据这个特点,可以估计刷新电路执行趟耗费的时间大致和访存时间相当 刷新放大器及读放大器均起此作用

上海市青少年算法2022年11月月赛(丙组)_宏阳李老师的博客-爱代码爱编程

T1. 奇偶数的判定 内存限制: 256 Mb时间限制: 1000 ms 题目描述 给定一个整数 n,若 n 是一个偶数,输出 even,若 n 是一个奇数,输出 odd。 输入格式 单个整数:表示 n。 输出格式 单个字符串:表示 n 的奇偶性 数据范围 −1,000,000≤n≤1,000,000 样例数据 输入: 0 输出: even 输入: -1

数据结构和常用排序算法复杂度_songwei4615的博客-爱代码爱编程

1.顺序表 插入操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数n/2 删除操作时间复杂度 最好O(1),最坏O(n),平均O(n) 移动结点的平均次数(n-1)/2 按值查找时间复杂度

数据结构与算法基础-学习-02-线性表之顺序表-插入元素、删除元素_阳光九叶草lxgzxj的博客-爱代码爱编程

一、测试环境 名称值cpu12th Gen Intel® Core™ i7-12700H操作系统CentOS Linux release 7.9.2009 (Core)内存3G逻辑核数2gcc 版本4.8.5 201506

第五章《类的继承》第3节:方法的重写_穆哥学堂的博客-爱代码爱编程

重写是子类对父类方法的实现过程进行重新编写。重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。 5.3.1方法重写的意义及实现方式 从理论上来讲,子类能够继承父类的所有属性和方法。当子类继承了父类的某个方法后,如果发现这个方法并不适合自己,或者是这个方法的算法效率较低,那么对于子类来说,这个从父类继承而来的

2022大厂面试秘籍java岗:中间件+算法+http+线程+虚拟机+分布式_啊码的博客-爱代码爱编程

前言 很多朋友对面试不够了解,不知道如何准备,对面试环节的设置以及目的不够了解,因此成功率不高。通常情况下校招生面试的成功率低于1%,而社招的面试成功率也低于5%,所以对于候选人一定要知道设立面试的初衷以及每个环节的意义,

【算法】传智杯练习赛:时钟_图灵机z的博客-爱代码爱编程

[传智杯 #5 练习赛] 时钟 题目描述 你有一个电子钟,可以显示 0:00 到 23:59 之间的所有时间,以数字的形式显示。其中小时是 0 到 23(0 时会显示一个 0,而 1 到 9 时不会显示前导 0),分钟是

c. almost all multiples(贪心 + 思维)_wyw___的博客-爱代码爱编程

Problem - C - Codeforces 给定两个整数n和x,如果pi是i的倍数,所有1≤i≤n-1,pn=1,且p1=x,则长度为n的排列组合† p被称为搞笑。 找出最小的有趣的排列组合,或报告说不存在这样的排列组合。 † 长度为n的排列组合是一个由1到n的每个整数精确地组成的数组。 ‡ 让a和b是长度为n的排列组合。如果在a和b不同

李宏毅:life long learning_bulibuli蛋的博客-爱代码爱编程

Life Long Learing 也是continual Learning,也是incremental learning 目录 Life-Long Learning  vs  Transfer Learning Evaluation Research Directions Selective Synaptic Plasticity——Regul

期末复习 c语言再学习_aniyaaaaa_的博客-爱代码爱编程

作者:@小萌新 专栏:@C语言复习 作者简介: 大二学生 希望能和大家一起进步! 本篇博客简介:回顾之前的分支循环以及一些题目博客 @[TOC](这里写目录标题 分支循环选择switch caseget

【csdn竞赛】第十期解题报告_icehomegre的博客-爱代码爱编程

文章目录 感想关于自己关于平台 第一题 (难度:入门)题目描述100分做法 第二题 (难度:简单)题目描述100分做法 第三题 (难度:中等/困难)题目描述100分做法1(对应中等)10

c++类与对象(一)-爱代码爱编程

目录 一、面向过程和面向对象认识 二、类的引入 三、类的定义 类的两种定义方式: 四、类的访问限定符及封装 4.1 访问限定符 4.2 封装 五、类的作用域 六、类的实例化 七、类对象模型 7.1 如何计算类对象的大小​​​​​ 7.2 类对象的存储方式 7.3 结构体内存对齐规则 八、this指针 8.1 this指针的引出