【CSP-S 2019】【洛谷P5665】划分【单调队列dp】

前言

c s p csp csp时发现自己做过类似这道题的题目 : P4954 [USACO09Open] Tower of Hay 干草塔
然后回忆了差不多 15 m i n 15min 15min才想出来。。。
然后就敲了 88 p t s 88pts 88pts的部分分。当时的内存是 950 M B 950MB 950MB左右,写一个高精就炸内存了。


题目

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

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

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

∑ i = 1 k 1 a i ≤ ∑ i = k 1 + 1 k 2 a i ≤ ⋯ ≤ ∑ i = k p + 1 n a i \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 i=1k1aii=k1+1k2aii=kp+1nai

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

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

( ∑ i = 1 k 1 a i ) 2 + ( ∑ i = k 1 + 1 k 2 a i ) 2 + ⋯ + ( ∑ i = k p + 1 n a i ) 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 (i=1k1ai)2+(i=k1+1k2ai)2++(i=kp+1nai)2

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


思路:

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


代码:

#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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值