洛谷 P4272 - [CTSC2009]序列变换(堆)
u1s1 在我完成这篇题解之前,全网总共两篇题解,一篇使用的平衡树,一篇使用的就是这篇题解讲解的这个做法,但特判掉了一个点,把特判去掉在 BZOJ 上会 WA 一个点。
两篇题解都异常简略,前一篇题解甚至只有代码没有说明,所以这里我来写篇比较详细题解造福人类了(大雾
建议没做过这题的同学先看看这道题,看题解区第一个做法,对这题有比较大的启发。
注:下文中分别用 \(a,b,l,r\) 代替题面中的 \(X,Y,A,B\)。
首先关于这题咱们可以想到一个非常 naive 的 DP,\(dp_{i,j}\) 表示考虑到前两个数,\(b_i=j\) 的 \(\sum\limits_{k=1}^i|a_k-b_k|\)。那么显然有 DP 转移 \(dp_{i,j}=\min\limits_{k=l}^rdp_{i-1,j-k}+|a_i-j|\)。一脸不好直接维护的样子。不过注意到绝对值函数是一个下凸函数,因此我们猜测对于一个固定的 \(i\),\(dp_{i,j}\) 随 \(j\) 的增大也是一个下凸函数,事实也的确如此,证明大概可以归纳,可能需要一些分类讨论,这里不再赘述,留给读者自己思考。
我们还可以注意到,每一轮中每条直线斜率变化的绝对值最多为 \(1\),也就是说任意时刻,下凸壳中直线斜率绝对值的最大值顶多为 \(n\),因此我们考虑维护 \(2n+2\) 个分界点的横坐标,具体来说,记 \(x_i\) 为第 \(i\) 个分界点,那么以凸包上横坐标分别为 \(x_i,x_{i+1}\) 的点组成线段刚好是凸包上斜率为 \(i-n-2\) 的直线,当然也有可能 \(x_i=x_{i+1}\),此时凸包上不存在斜率为 \(i-n-2\)(说白了就是记录凸包拐点的横坐标)。
接下来考虑加入一个 \(a_i\) 后会对凸包产生怎样的影响。我们考虑将每一轮转移的过程分成两部分:\(dp_{i,j}=\min\limits_{k=l}^rdp_{i-1,j-k}\) 和 \(dp_{i,j}:=dp_{i,j}+|a_i-j|\),首先对于第一部分而言,显然在凸包上存在一些分界点,满足分界点前面单调不增,分界点之后单调不降,不难发现这些分界点组成的集合就是斜率为 \(0\) 的直线。假设斜率为 \(0\) 的直线的两个端点横坐标为 \(u,v(u,v)\),那么不难发现对于 \(j\le u+l\),由于 \(u\) 前面单调递减,因此我们肯定会尽量选择 \(j\) 与接近的点转移,即 \(dp_{i,j-l}\),因此我们可以得到,进行第一步操作后,凸包上 \(u\) 前面的点都会向右平移 \(l\) 格,同理在 \(v+r\) 后面的点 \(j\),我们肯定会尽量选离 \(j\) 远的点的即 \(j-r\),也就是说 \(v\) 后面的点肯定会向右平移 \(r\) 格,至于中间的部分……那显然还是一条水平的直线咯(
第二部分就相对比较简单了,不难发现 \(f(x)=|a_i-x|\) 在 \((-\infty,a_i)\) 中是斜率为 \(-1\) 的直线,\((a_i,\infty)\) 中是斜率为 \(1\) 的直线,因此 \(a_i\) 前面的部分的直线的斜率会减 \(1\),后面的部分斜率会加 \(1\)。这样我们可以看作是在 \(a_i\) 处插入了两个断点。
考虑怎样维护这个东西,我们开两个堆 \(L,R\) 分别存储斜率为 \(0\) 的直线前面和后面的断点,那么时刻 \(i\) 时显然 \(L\) 应当为存储的是最小的 \(i\) 个断点,\(R\) 应当存储最大的 \(i\) 个断点,但有时并不一定真的存储的就是最小/最大的 \(i\) 个断点,这时候我们就要进行调整,具体方法就是取出 \(L\) 中最大的元素 \(x\) 和最小的元素 \(y\),如果 \(x>y\) 就将 \(x\) 插入 \(R\),\(y\) 插入 \(L\),否则 break
。还有一个问题就是如何处理坐标平移,具体方法就在 \(i\) 时刻是将 \(x\) 插入 \(L\) 时我们不插入 \(x\) 本身,而是插入 \(x-(i-1)l\),这样在 \(i’\) 时刻这个点真正的坐标就是 \(x+i’l\),不难发现这里用了差分的思想,在统计每个点有多少个时刻满足 xxxx 条件时也用到了类似的思想。
时间复杂度 \(n\log n\)
注意加一些特判,否则在 BZOJ 上会 WA 一个点,进而取得 \(0\) 分的好成绩(London Fog
const int MAXN=5e5;
int n,mx,l,r,a[MAXN+5],b[MAXN+5];ll ext=0;
priority_queue<ll> L;
priority_queue<ll,vector<ll>,greater<ll> > R;
//void prt(auto q){
// while(!q.empty()) printf("%d ",q.top()),q.pop();
// printf("\n");
//}
int main(){
scanf("%d%d%d%d",&n,&mx,&l,&r);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]<1ll*(i-1)*l+1){
ext+=abs(a[i]-(1ll*(i-1)*l+1));
a[i]=1ll*(i-1)*l+1;
}
}
for(int i=1;i<=n;i++){
L.push(a[i]-1ll*(i-1)*l);
R.push(a[i]-1ll*(i-1)*r);
while(1){
ll x=L.top();x+=1ll*(i-1)*l;
ll y=R.top();y+=1ll*(i-1)*r;
// printf("%lld %lld\n",x,y);
if(x<=y) break;L.pop();R.pop();
L.push(y-1ll*(i-1)*l);R.push(x-1ll*(i-1)*r);
} b[i]=L.top()+1ll*(i-1)*l;
// prt(L);prt(R);
} ll res=0;chkmin(b[n],mx);
chkmax(b[n],1ll*l*(n-1)+1);//be careful
for(int i=n-1;i;i--){
if(b[i]<b[i+1]-r) b[i]=b[i+1]-r;
if(b[i]>b[i+1]-l) b[i]=b[i+1]-l;
}
// if(n==8888) for(int i=1;i<=n;i++) printf("%d%c",b[i]," \n"[i==n]);
for(int i=1;i<=n;i++) res+=abs(a[i]-b[i]);
printf("%lld\n",res+ext);
return 0;
}
洛谷 P4272 - [CTSC2009]序列变换(堆)的更多相关文章
- 【洛谷P1483】序列变换
题目大意:给定一个长度为 N 的序列,有 M 个操作,支持将下标为 x 的倍数的数都加上 y,查询下标为 i 的元素的值. 题解:由于查询操作很少,相对的,修改操作很多.若直接模拟修改操作,即:枚举倍 ...
- 洛谷 P1628 合并序列
洛谷 P1628 合并序列 题目传送门 题目描述 有N个单词和字符串T,按字典序输出以字符串T为前缀的所有单词. 输入格式 输入文件第一行包含一个正整数N: 接下来N行,每行一个单词,长度不超过100 ...
- 洛谷 P5470 - [NOI2019] 序列(反悔贪心)
洛谷题面传送门 好几天没写题解了,写篇题解意思一下(大雾 考虑反悔贪心,首先我们考虑取出 \(a,b\) 序列中最大的 \(k\) 个数,但这样并不一定满足交集 \(\ge L\) 的限制,因此我们需 ...
- [洛谷P1032] 字串变换
洛谷题目链接:字串变换 题目描述 已知有两个字串 A, B 及一组字串变换的规则(至多6个规则): A1 -> B1 A2 -> B2 规则的含义为:在 A$中的子串 A1 可以变换为 B ...
- BZOJ 1500 洛谷2042维护序列题解
BZ链接 洛谷链接 这道题真是丧心病狂.... 应该很容易就可以看出做法,但是写代码写的....... 思路很简单,用一个平衡树维护一下所有的操作就好了,重点讲解一下代码的细节 首先如果按照常规写法的 ...
- 【洛谷 P1631】 序列合并 (堆)
题目链接 直接暴力搞是\(n\)方的复杂度.\(n^2\)个数选\(n\)个最小的,容易想到堆. 我们堆里记录两个信息:到\(A\)数组哪个位置了,到\(B\)数组哪个位置了, 我直接把这两个信息存在 ...
- 【洛谷P1963】[NOI2009]变换序列(二分图匹配)
传送门 题意: 现有一个\(0\)到\(n-1\)的排列\(T\),定义距离\(D(x,y)=min\{|x-y|,N-|x-y|\}\). 现在给出\(D(i, T_i)\),输出字典序最小的符合条 ...
- 洛谷P4165 [SCOI2007]组队(排序 堆)
题意 题目链接 Sol 跟我一起大喊:n方过百万,暴力踩标算! 一个很显然的思路是枚举\(H, S\)的最小值算,复杂度\(O(n^3)\) 我们可以把式子整理一下,变成 \[A H_i + B S_ ...
- 洛谷P3378 【模板】堆
P3378 [模板]堆 160通过 275提交 题目提供者HansBug 标签 难度普及- 提交 讨论 题解 最新讨论 经实际测试 堆的数组开3000- 题目有个问题 为什么这个按课本堆标准打的- ...
随机推荐
- 【UE4 C++】 Datatable 读写、导入导出 CSV/Json
Datatable 读取行数据 1. 创建结构体 继承自 FTableRowBase USTRUCT(BlueprintType) struct FSimpleStruct :public FTabl ...
- 【UE4 C++】 UDataAsset、UPrimaryDataAsset 的简单使用
UDataAsset 简介 用来存储数据,每一个DataAsset 都是一份数据 可以派生,系统自带派生 UPrimaryDataAsset 方便数据对象的加载和释放 可以引用其他的 UDataAss ...
- Java中类及方法的加载顺序
1. 虚拟机在首次加载Java类时,会对静态代码块.静态成员变量.静态方法进行一次初始化(静态间按顺序执行). 2. 只有在调用new方法时才会创建类的实例. 3. 类实例创建过程:父子继承关系,先父 ...
- 计算机网络之传输层UDP协议
文章转自:https://blog.csdn.net/weixin_43914604/article/details/105453096 学习课程:<2019王道考研计算机网络> 学习目的 ...
- Go语言核心36讲(Go语言进阶技术十一)--学习笔记
17 | go语句及其执行规则(下) 知识扩展 问题 1:怎样才能让主 goroutine 等待其他 goroutine? 我刚才说过,一旦主 goroutine 中的代码执行完毕,当前的 Go 程序 ...
- linux下uptime命令
https://man.linuxde.net/uptime uptime命令能够打印系统总共运行了多长时间和系统的平均负载.uptime命令可以显示的信息显示依次为:现在时间.系统已经运行了多长时间 ...
- populating-next-right-pointers-in-each-node leetcode C++
Given a binary tree struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *nex ...
- hdu 1848 Fibonacci again and again (SG)
题意: 3堆石头,个数分别是m,n,p. 两个轮流走,每走一步可以选择任意一堆石子,然后取走f个.f只能是菲波那契中的数(即1,2,3,5,8.....) 取光所有石子的人胜. 判断先手胜还是后手胜. ...
- 线程私有数据TSD——一键多值技术,线程同步中的互斥锁和条件变量
一:线程私有数据: 线程是轻量级进程,进程在fork()之后,子进程不继承父进程的锁和警告,别的基本上都会继承,而vfork()与fork()不同的地方在于vfork()之后的进程会共享父进程的地址空 ...
- Fiddler抓包工具简介:(三)手机端代理配置
1.接入网络:需要在移动终端(手机或pad)上指定代理服务器为Fiddler所在主机的IP,端口默认为8888,要保证手机和安装有fiddler的电脑处在同一局域网内,手机能ping通电脑. [方法] ...