首页 > 其他分享 >NFLS NOI模拟 序列

NFLS NOI模拟 序列

时间:2024-05-23 15:42:16浏览次数:27  
标签:totadd maxid NOI res mid len NFLS 区间 序列

涉及知识点:分治、贪心

前言

没错……又是一道叫序列的题……

题意

有一个长为 \(n\ (\leq10^5)\) 的序列 \(a\),你可以花费 \(x^2\) 的代价将 \(a_i\) 变成 \(a_i+x\),使得代价加上 \(a\) 两两数之差的绝对值乘以一个给定常数 \(c\) 的总和最小。

思路

拿到手觉得是一个贪心,但是直接贪有很多阻碍,一个数变大需要考虑很多因素。但我们发现,从最小的那个数入手相对来说更简单一些,最小的数变大,直到赶上第二小的数之前,代价的两个影响因素都是单调变化的——差值越来越小、\(x^2\) 越来越大。

假设 \(a_3\) 一直变大更优,直到 \(a_2=a_3\)。

那这时,\(a_3\) 继续变大还会优吗?

很明显,\(a_3\) 再变大不优了,虽然 \(a_4-a_3\) 的差值变小了 \(1\),但是 \(a_3-a_2\) 的差值也变大了 \(1\),二者抵消,但是我们还会多付出 \(a_3\) 变大的 \(x^2\) 的代价。因此我们总结出:当自己增加到和左右相邻的某个数一样大的时候,那么两个数之后只会要么一起增加要么一起停止才优。

形象地说,我们要将最底下的数往上推,推到旁边有一样高的就带着它一起推,直到推不动为止。

但是事情没这么简单,当遇到下面这种例子时,就不能简单的从最小的数向上一次推完。

我们可以用分治:每次找到区间内最大的数记为 \(a_{maxid}\),以它为基准将区间划为左右部分,递归左右部分查询它们是否能够到 \(a_{maxid}\)​ 的高度,如果两边都可以的话就可以将当前这个大区间视为一个整体往上推,否则不优。

可以证明此时大区间左右相邻的数一定都大于等于区间内所有数,不会出现大区间推的比旁边还高的情况。

而如何查询能否到某个高度呢?我们发现向上推 \(h\) 层与推到 \(h+1\) 的区别在于,差值的贡献会减小 \(2c\) 或者 \(c\)(如果是 \(a_1\) 或 \(a_n\)),而每个数的代价会增多 \((x+1)^2-x^2=2x+1\),因为此时差值和代价是满足单调性的,所以其实我们只用找代价增大值大于差值减小值的第一个 \(h\rightarrow h+1\) 即可,这个分界点可以二分求出,意味着这个整体能继续向上最多推 \(h\) 层。

实现

以上叙述了大体思路,下面具体讲述分治和二分的具体实现。

变量名意义:

n,c,a: 同题面
l,r,len: 分治的区间及区间长度
aimh: 区间整体希望达到的高度
maxid: 同上文,区间内最大的数的下标
resl,resr: 左右子区间分治的返回值,为一个pair,first为子区间内增加的高度总和(见下文),second为子区间最大能达到的高度
totadd: 当前区间增加的高度总和(见下文)
pos: 当前区间是否左右两边都相邻有数
L,R,mid,res: 二分用变量
ans: 总答案,初始值为不经过任何修改的差值总和,每次修改后将减小的差值加上去得到最终答案。
  1. 首先判断一下是不是已经分治完了。

    if(l>r) return mkp(0,-1);
    
  2. 获取左右子区间的返回值,如果无法到达 a[maxid],那么整个区间就没法一起向上推了。

    int maxid=getmaxid(l,r);
    pii resl=solve(l,maxid-1,a[maxid]),resr=solve(maxid+1,r,a[maxid]);
    if((resl.second!=-1 && resl.second!=a[maxid]) || (resr.second!=-1 && resr.second!=a[maxid])) return mkp(0,0);
    
  3. 二分找到第一个代价增大值大于差值减小值的增加高度(是增加的高度不是总高度!)。

    LL totadd=resl.first+resr.first,pos=(l==1||r==n)?1:2;
    int L=0,R=MAXA,mid,res=0,len=r-l+1;
    while(L<=R){
        mid=(L+R)/2;
        if(2*(totadd+mid*len)+len > pos*c){
            res=mid;
            R=mid-1;
        }
        else L=mid+1;
    }
    
    • 如何理解 2*(totadd+mid*len)+len 为代价增大值?

      我们要清楚所谓 \((x+1)^2-x^2=2x+1\) 中的 \(x\) 是该点已经增加过的高度值而非该点的高度,假设某个点 \(a_i\) 已经增加过 \(\Delta h_i\),那么它高度再增加 \(1\) 的代价为 \(2\Delta h_i+1\)。所以我们记录下该区间内所有点已经增加过的高度值记为 \(totadd\),另外由于在二分中我们计算的是 \(mid\rightarrow mid+1\) 这轮的代价,所以区间内每个点还得“假设”增高了 \(mid\),因此计算时我们直接用乘法分配率整合了区间内所有点已经增加过的高度 \(x=totadd+mid*len\)。后面加的 \(len\) 很好理解,一个点增高最后要 \(+1\),那 \(len\) 个点增高总共就是加 \(len\)。

    • 如何理解 pos*c 为差值减小值?

      如果它在边上(\(a_1\) 或 \(a_n\)),那么它变化只会影响和一个数的差值,否则为两个。

  4. 二分结束后,如果无论 \(mid\)​ 怎么取都不优,那么干脆就不增加了,返回。

    if(L<1) return mkp(totadd,a[maxid]);
    
  5. 如果可以增加很多甚至超过了希望达到的高度(区间相邻的点的高度),那没必要,只需要增加到一样高即可。

    res=min(res,aimh-a[maxid]);
    
  6. 统计对答案的贡献。

    ans+=((2*totadd+len)+(2*(totadd+(res-1LL)*len)+len))*res/2 - pos*res*c;
    
    • 如何理解 ((2*totadd+len)+(2*(totadd+(res-1LL)*len)+len))*res/2

      这是一个等差数列求和,2*totadd+len 为起点,即该区间作为整体向上推一次时的贡献;2*(totadd+(res-1LL)*len)+len) 为终点,即该区间作为整体向上推第 \(res-1\) 次的贡献。至于这东西为什么是等差数列是容易证明的。

    • 如何理解 pos*res*c

      很明显,这是这个区间作为整体与它相邻点差值的减小值。

  7. 返回。

    return mkp(totadd+1LL*res*len,a[maxid]+res);
    

代码

#include<bits/stdc++.h>
#define mkp make_pair
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
    static char ch[1<<20],*l,*r;
    return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
    T res=0,f=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
    x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
    static char wtbuff[20];
    static int wtptr;
    if(x==0){
        putchar('0');
    }
    else{
        if(x<0){x=-x;putchar('-');}
        wtptr=0;
        while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
        while(wtptr--) putchar(wtbuff[wtptr]);
    }
    if(endch!='\0') putchar(endch);
}
typedef long long LL;
typedef pair<int,int> pii;
const int MAXN=1e5+5,MAXA=1e6+5,MAXB=18;
int n,c,a[MAXN],lg[MAXN];
LL ans=0;
pii st[MAXN][MAXB];
inline pii getmax(const int& l,const int& r){
    int lglen=lg[r-l+1];
    return max(st[l][lglen],st[r-(1<<lglen)+1][lglen]);
}
pii solve(int l,int r,int aimh){
    if(l>r) return mkp(0,-1);
    int len=r-l+1,maxid=getmax(l,r).second;
    pii resl=solve(l,maxid-1,a[maxid]),resr=solve(maxid+1,r,a[maxid]);
    if((resl.second!=-1 && resl.second!=a[maxid]) || (resr.second!=-1 && resr.second!=a[maxid])) return mkp(0,0);
    LL totadd=resl.first+resr.first,pos=(l==1||r==n)?1:2;
    int L=0,R=MAXA,mid,res=0;
    while(L<=R){
        mid=(L+R)/2;
        if(2*(totadd+mid*len)+len > pos*c){
            res=mid;
            R=mid-1;
        }
        else L=mid+1;
    }
    if(L<1) return mkp(totadd,a[maxid]);
    res=min(res,aimh-a[maxid]);
    ans+=((2*totadd+len)+(2*(totadd+(res-1LL)*len)+len))*res/2 - pos*res*c;
    return mkp(totadd+1LL*res*len,a[maxid]+res);
}
int main(){
    // freopen("seq.in","r",stdin);
    // freopen("seq.out","w",stdout);
    rd(n);rd(c);
    lg[1]=0;
    for(int i=2;i<=n;i++) lg[i]=lg[i/2]+1;
    for(int i=1;i<=n;i++){
        rd(a[i]);
        st[i][0]=mkp(a[i],i);
        if(i>1) ans+=1LL*c*abs(a[i]-a[i-1]);
    }
    for(int j=1;j<MAXB;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
        }
    }
    solve(1,n,getmax(1,n).first);
    wt(ans);
    return 0;
}

标签:totadd,maxid,NOI,res,mid,len,NFLS,区间,序列
From: https://www.cnblogs.com/SkyNet-PKN/p/18208579

相关文章

  • CSP历年复赛题-P1046 [NOIP2005 普及组] 陶陶摘苹果
    原题链接:https://www.luogu.com.cn/problem/P1046题意解读:30+伸手的高度,能够得着几个苹果。解题思路:直接模拟。100分代码:#include<bits/stdc++.h>usingnamespacestd;inta[15],h,ans;intmain(){for(inti=1;i<=10;i++)cin>>a[i];cin>>h;......
  • CSP历年复赛题-P1087 [NOIP2004 普及组] FBI 树
    原题链接:https://www.luogu.com.cn/problem/P1087题意解读:字符串作为根,左边一半作为左子树,右边一半作为右子树,递归构造数,并按FBI规则输出后续遍历结果。解题思路:按照题意,通过dfs来构造树,对于字符串str,提取左边一半递归构造左子树,提取右边一半递归构造右子树,前提是字符串长度>1......
  • CSP历年复赛题-P1085 [NOIP2004 普及组] 不高兴的津津
    原题链接:https://www.luogu.com.cn/problem/P1085题意解读:找到两数之和大于8且两数之和最大值的位置解题思路:不多说,送分题,直接模拟法即可100分代码:#include<bits/stdc++.h>usingnamespacestd;inta,b;intmaxx,maxn;intmain(){for(inti=1;i<=7;i++)......
  • NFLS NOI模拟 树数术
    涉及知识点:树、倍增、单调栈题意给你一颗有\(n\(\leq7\times10^5)\)个节点的树,再给你一个长为\(m\(\leq7\times10^5)\)的序列,序列中的值代表树上点的编号,有\(q\)次询问,每次询问取原序列的\(k\(\sumk\leq7\times10^5)\)个子区间并连起来成为一段新的序列,问新的序列......
  • DeepMTS深度学习神经网络多元时间序列预测宏观经济数据可视化|附数据代码
    原文链接:https://tecdat.cn/?p=36237原文出处:拓端数据部落公众号在数据科学领域,时间序列分析一直是一个至关重要的研究方向,尤其在金融、气象、医学以及许多其他科学和工业领域中,准确的时间序列预测对于制定策略、政策规划以及资源管理都具有极其重要的意义。随着技术的不断进步,......
  • CSP历年复赛题-P1044 [NOIP2003 普及组] 栈
    原题链接:https://www.luogu.com.cn/problem/P1044题意解读:一组数入栈、出栈的方案数,如果了解卡特兰数,此题可以秒杀;如果不了解,也可以通过递归或者递推来解决;最次,可以通过DFS暴搜出方案数,当然对于n个数,一共有n次入栈、n次出栈,一共2n次,每次要么入栈要么出栈,总搜索次数在22n规模,n最......
  • CSP历年复赛题-P1045 [NOIP2003 普及组] 麦森数
    原题链接:https://www.luogu.com.cn/problem/P1045题意解读:要计算2p-1的位数和最后500位,实际上只需要计算2p,两者位数一致,前者比后者个位减1即可,且个位肯定不会是0,比较容易处理。解题思路:如果直接采用高精度乘法计算2p,p最大3.1*106,高精度所用数组最长大概9*105,一共最多计算3.......
  • CSP历年复赛题-P1043 [NOIP2003 普及组] 数字游戏
    原题链接:https://www.luogu.com.cn/problem/P1043题意解读:将n个环形数分成任意m组,组内求和再%10、负数转正,组间相乘,求所有分组方案中得到结果的最小值和最大值。解题思路:比赛题的首要目的是上分!此题一看就是DP,但是苦苦思索了半天,想不清楚状态表示,那么可以换换策略,先暴力得分再......
  • loj#575. 「LibreOJ NOI Round #2」不等关系
    记事件\(A\)为「当\(s_i=\texttt<\)时\(p_i<p_{i+1}\)」,事件\(B\)为「当\(s_i=\texttt<\)时\(p_i<p_{i+1}\),且存在\(s_j=\texttt>\),满足\(p_i<p_{i+1}\)。所求即\(n(A)-n(B)\)。\(n(A)\)是好求的,相当于部分定序排列,记每个递增段的长度为\(a_1......
  • 序列化和反序列化
    1.什么是序列化和反序列化?序列化:把对象转化为可传输的字节序列过程称为序列化。反序列化:把字节序列还原为对象的过程称为反序列化。2.序列化的目的? 序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的......