zoukankan      html  css  js  c++  java
  • 【CSP-S 2019】【洛谷P5665】划分【单调队列dp】

    前言

    cspcsp时发现自己做过类似这道题的题目 : P4954 [USACO09Open] Tower of Hay 干草塔
    然后回忆了差不多15min15min才想出来。。。
    然后就敲了88pts88pts的部分分。当时的内存是950MB950MB左右,写一个高精就炸内存了。


    题目

    2048 年,第三十届 CSP 认证的考场上,作为选手的小明打开了第一题。这个题的样例有 nn 组数据,数据从 1n1 sim n 编号,ii 号数据的规模为 aia_i

    小明对该题设计出了一个暴力程序,对于一组规模为 uu 的数据,该程序的运行时间u2u^2。然而这个程序运行完一组规模为 uu 的数据之后,它将在任何一组规模小于 uu 的数据上运行错误。样例中的 aia_i 不一定递增,但小明又想在不修改程序的情况下正确运行样例,于是小明决定使用一种非常原始的解决方案:将所有数据划分成若干个数据段,段内数据编号连续,接着将同一段内的数据合并成新数据,其规模等于段内原数据的规模之和,小明将让新数据的规模能够递增。

    也就是说,小明需要找到一些分界点 1k1<k2<<kp<n1 leq k_1 lt k_2 lt cdots lt k_p lt n,使得

    i=1k1aii=k1+1k2aii=kp+1nai sum_{i=1}^{k_1} a_i leq sum_{i=k_1+1}^{k_2} a_i leq cdots leq sum_{i=k_p+1}^{n} a_i

    注意 pp 可以为 00 且此时 k0=0k_0 = 0,也就是小明可以将所有数据合并在一起运行。

    小明希望他的程序在正确运行样例情况下,运行时间也能尽量小,也就是最小化

    (i=1k1ai)2+(i=k1+1k2ai)2++(i=kp+1nai)2 (sum_{i=1}^{k_1} a_i)^2 + (sum_{i=k_1+1}^{k_2} a_i)^2 + cdots + (sum_{i=k_p+1}^{n} a_i)^2

    小明觉得这个问题非常有趣,并向你请教:给定 nnaia_i,请你求出最优划分方案下,小明的程序的最小运行时间。


    思路:

    假设我们现在已经划分为三个部分x,y,zx,y,z满足xyzxleq yleq z,那么显然是有
    x2+y2+z2<x2+(y+z)2x^2+y^2+z^2<x^2+(y+z)^2
    所以我们肯定要做到能分就分
    但是贪心选取肯定是错误的。样例一明显就指出了错误。
    考虑dpdp。设f[i]f[i]表示我们划分1i1sim i的所有数,以ii为最后一个区块的情况下,最后一个区块的最小长度。
    那么枚举一个jj,明显有
    f[i]=min(k=jiak)   (k=jiakf[j])f[i]=min(sum^{i}_{k=j}a_k) (sum^{i}_{k=j}a_kgeq f[j])
    由于k=jiaksum^{i}_{k=j}a_k满足单调性,所以肯定选择尽量大的jj来转移。
    如果jj可以转移到ii,那么转移的同时可以求出划分的费用
    ans[i]=ans[j1]+(k=jiak)2ans[i]=ans[j-1]+(sum^{i}_{k=j}a_k)^2
    这样我们就得到了一个O(n2)O(n^2)的算法,可以得到64pts64pts的高分。
    我们发现,转移的条件k=jiakf[j]sum^{i}_{k=j}a_kgeq f[j]其实就是k=1iakk=jiakf[j]sum^i_{k=1}a_k-sum^i_{k=j}a_kgeq f[j],移项就得到了k=1iakf[j]+k=jiaksum^i_{k=1}a_kgeq f[j]+sum^i_{k=j}a_k
    我们发现,在做了前缀和之后,上式等号左边只与ii有关,等号右边只与jj有关。同时我们又要满足选择尽量大的jj来转移,所以就可以维护一个单调队列装f[j]+k=jiakf[j]+sum^i_{k=j}a_k进行转移。
    但是我们每次要选择的是满足k=1iakf[j]+k=jiaksum^i_{k=1}a_kgeq f[j]+sum^i_{k=j}a_k的尽量大jj进行转移,而不是单纯的最小的k=1iakf[j]+k=jiaksum^i_{k=1}a_kgeq f[j]+sum^i_{k=j}a_k转移。所以我们每次要不断弹出队头,知道队头不再满足k=1iakf[j]+k=jiaksum^i_{k=1}a_kgeq f[j]+sum^i_{k=j}a_k。此时将最后一次弹出的元素再从头部插入进行转移。这样就保证了每次选择jj最大的满足条件的元素进行转移。容易证明,弹出的元素不会对后面的转移产生影响。
    这样每个元素最多进入队列1次,时间复杂度O(n)O(n)
    这样我们就得到了88pts88pts的高分。
    对于type=1type=1的数据点需要使用高精,但是由于O(n)O(n)的算法我们的内存已经使用了950MB950MB,所以几乎没有空间来敲高精。
    所以此时就只能用csp不允许使用的__int128了
    我们将ansans改为__int128\_\_int128类型,是可以存下最终答案的。
    然后我就愉快的T了。
    在这里插入图片描述
    在尝试过所有我知道的化学性卡常后,样例三依然需要2.4s+2.4s+才可以跑过。
    所以此时就只能用csp不允许使用的Ofast了
    物理性卡常nbnb!样例最终可以在0.85s0.85s左右跑过。
    然后我就愉快的MLE了。
    在这里插入图片描述
    经过输出后,ans,f,sumans,f,sum三个数组加起来是1200+MB1200+MB。将近200MB200MB的差距,只能考虑删除一个数组了。
    ansanssumsum是肯定无法删除的,而ff我们发现,在转移时是等于sum[i]sum[last]sum[i]-sum[last]的,其中lastlast直可以转移的最大的jj
    所以我们考虑直接用sumsum数组来表示出ff数组。这样我们往单调队列插入时就要插入i,fi+sumii,f_i+sum_i两维,因为后者在去掉ff数组后是没办法算出来的。
    最终还是以1.38s,804.98MB1.38s,804.98MB过了这道题。但是在cspcsp时是不允许用__int128\_\_int128OfastOfast的,所以其实这份代码无论是时间还是空间都是过不去的


    代码:

    #pragma GCC optimize("Ofast")
    #pragma GCC optimize("inline")
    #include <queue>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <algorithm>
    #define mp make_pair
    using namespace std;
    typedef long long ll;
    
    const int N=40000010,MOD=(1<<30),M=100010;
    int n,x,type,cnt,id;
    ll d,xx,yy,zz,m,sum[N],b[4],p[M],l[M],r[M];
    __int128 ans[N];
    pair<int,ll> last;
    char ch;
    deque<pair<int,ll> > q;
    
    inline ll read()
    {
    	d=0; ch=getchar();
    	while (!isdigit(ch)) ch=getchar();
    	while (isdigit(ch))
    		d=(d<<3)+(d<<1)+ch-48,ch=getchar();
    	return d;
    }
    
    inline void write(__int128 x)
    {
    	if (x>9) write(x/10);
    	putchar(x%10+48);
    }
    
    inline ll Get(int i)
    {
    	int id=(i-1)%3+1;
    	if (i>2 && id==1) b[1]=(xx*b[3]+yy*b[2]+zz)%MOD;
    	if (i>2 && id==2) b[2]=(xx*b[1]+yy*b[3]+zz)%MOD;
    	if (i>2 && id==3) b[3]=(xx*b[2]+yy*b[1]+zz)%MOD;  //减少模运算次数
    	if (i>p[cnt]) cnt++;
    	return (b[id]%(r[cnt]-l[cnt]+1))+l[cnt];
    }
    
    int main()
    {
    	scanf("%d%d",&n,&type);
    	if (type)
    	{
    		xx=read(); yy=read(); zz=read(); b[1]=read(); b[2]=read(); m=read();
    		for (int i=1;i<=m;i++)
    			p[i]=read(),l[i]=read(),r[i]=read();
    	}	
    	q.push_back(mp(0,0));
    	for (register int i=1;i<=n;i++)
    	{
    		sum[i]=sum[i-1]+(type?Get(i):read());
    		while (q.size() && sum[i]>=q.front().second)
    		{
    			last=q.front();
    			q.pop_front();
    		}
    		q.push_front(last);
    		int pos=last.first;
    		ans[i]=ans[pos]+(__int128)(sum[i]-sum[pos])*(sum[i]-sum[pos]);
    		while (q.size() && q.back().second>=sum[i]-sum[pos]+sum[i]) q.pop_back();
    		q.push_back(mp(i,sum[i]-sum[pos]+sum[i]));
    	}
    	write(ans[n]);
    	return 0;
    }
    
  • 相关阅读:
    linux 运维
    mariadb replication
    phpmyadmin
    Objective-C设计模式——单例Singleton(对象创建)
    收藏iOS学习资料
    axios拦截器
    vue单页面优化
    html设置http缓存代码
    js数组去重,排序的几种方法
    前端移动端问题
  • 原文地址:https://www.cnblogs.com/hello-tomorrow/p/11997980.html
Copyright © 2011-2022 走看看