[2019 CSP-S Day2 T2]划分
发布日期:2021-05-07 01:00:45 浏览次数:27 分类:精选文章

本文共 5494 字,大约阅读时间需要 18 分钟。

题目

骗分思路:动态规划

暴力的方法

枚举每个划分点。 O ( \mathcal O( O(应该过不了 ) ) )

动态规划

f ( x , y ) f(x,y) f(x,y) 表示最后一段是 ( x , y ] (x,y] (x,y] ,将前 y y y 个数完成划分的总花费最小值。

那么 f ( x , y ) = min ⁡ z = 0 x − 1 f ( z , x ) [ ∑ r = x + 1 y a r ≥ ∑ r = z + 1 x a r ] f(x,y)=\min_{z=0}^{x-1} f(z,x)\left[\sum_{r=x+1}^y a_r\ge\sum_{r=z+1}^x a_r\right] f(x,y)=z=0minx1f(z,x)[r=x+1yarr=z+1xar]

直接这样实现,复杂度就会是 O ( n 3 ) \mathcal O(n^3) O(n3)

真·动态规划

x x x 固定, y y y 在不断右移的过程中, z z z 的取值范围会逐渐左移。移动的同时取 min ⁡ \min min 值即可。

void solve(){   	for(int i=0; i<=n; ++i)		for(int j=0; j<=n; ++j)			dp[i][j] = infty;	dp[0][0] = 0;	for(int i=0; i<=n; ++i){   		long long sigmaJ = 0, sigmaK = 0;		int k = i; long long minK = dp[i][i];		for(int j=i+1; j<=n; ++j){   			sigmaJ += a[j];			while(k and sigmaK+a[k] <= sigmaJ){   				sigma += a[k];				minK = min(minK,dp[-- k][i]);			}			dp[i][j] = minK+sigmaJ*sigmaJ;		}	}}

正解:圣·动态规划

多充几包升个星马上就过了

使用神奇结论——最后一段越小越好

该怎么证明呢? 打表发现 我还是写个可能比较严谨的证明吧。

使用数学归纳法。将此条件记为性质 P P P

对于右端点为 1 1 1 的情况,显然成立(毕竟左端点只能是 1 1 1 )。

倘若右端点严格小于 k k k 的情况都已经满足性质 P P P ;对于右端点为 k k k 的情况,考虑两种不同的划分,满足:最后一段是指定的,其余是最优

先证明几个小的结论。

均匀原理

如果 a + b = c + d a+b=c+d a+b=c+d ∣ a − b ∣ < ∣ c − d ∣ |a-b|<|c-d| ab<cd,那么 a 2 + b 2 < c 2 + d 2 a^2+b^2<c^2+d^2 a2+b2<c2+d2 。也就是说,和一定,差越小越好。

证明

考虑 a , b a,b a,b a − x , b + x a-x,b+x ax,b+x (假设 0 ≤ x ≤ a ≤ b 0\le x\le a\le b 0xab )。

直接暴力计算右边那一组的贡献,为 ( a − x ) 2 + ( b + x ) 2 = a 2 + b 2 + 2 x ( x + b − a ) (a-x)^2+(b+x)^2=a^2+b^2+2x(x+b-a) (ax)2+(b+x)2=a2+b2+2x(x+ba)

由于 0 ≤ x ≤ a ≤ b 0\le x\le a\le b 0xab ,所以 2 x ( x + b − a ) > 0 2x(x+b-a)>0 2x(x+ba)>0 ,所以右边那一组的代价更高。而显然右边那一组中,两个数的差更大。

不包含原理

如果两个划分出来的区间 [ l 1 , r 1 ] , [ l 2 , r 2 ] [l_1,r_1],[l_2,r_2] [l1,r1],[l2,r2] 满足 r 1 ≤ r 2 < k r_1\le r_2<k r1r2<k ,那么 l 1 ≤ l 2 l_1\le l_2 l1l2

证明

在这里插入图片描述

譬如上图,两个红色的区间不满足上面的条件。(黑色的区间只是表示 r 2 < k r_2<k r2<k ,未必如图示)。

由于右端点小于 k k k 的点,都已经满足性质 P P P ,所以将红色的区间移动成蓝色的区间更优。因为一定有解(剩下的直接抄袭上面的方案)。与假设矛盾。

最后的最小

然后考虑两个不同的方案,谁会更优。

在这里插入图片描述
上面的方案,最后一段较大;下面的方案,最后一段较小。根据 不包含原理 ,红色的那一段 r r r 较大,所以绿色的区间的 l l l 较小。

想想,要是我们把 R e d \color{red}{Red} Red 的区间缩小为 B l u e \color{Blue}{Blue} Blue 的区间, B l a c k \color{Black}{Black} Black 的区间就会变大为 P i n k \color{Pink}{Pink} Pink 的区间。

原本 R e d \color{Red}{Red} Red 的区间,长度是比 B l a c k \color{Black}{Black} Black 的区间小的,根据 均匀原理 ,代价总和会变大。将这种“方案”记为方案三。——尽管这可能并不是合法的方案。

即使是这样,由于右端点小于 k k k 的区间已经满足性质 P P P(别忘了我们是数学归纳法!),显然 x < k x<k x<k ,而 B l u e \color{Blue}{Blue} Blue 的区间比 G r e e n \color{Green}{Green} Green 的区间更小,所以:上面的方案,比方案三的代价更大!

但是,方案三比方案二还要差,所以 上面的方案劣于下面的方案

代码优化

知道这个性质,如何优化我们的 D P \tt{DP} DP 呢?

谁说的可以不用动态规划了扔出去喂鱼

这说明我们代码中

minK = min(minK,dp[-- k][i]);

这一行鸟用没有!因为找到的第一个,就会是 min ⁡ \min min

更准确地说,如果 f ( y , x ) f(y,x) f(y,x) 可以往后转移, y y y 一定是某个只与 x x x 有关的值!那么我们存辣么多状态做什麼?

g ( x ) g(x) g(x) 表示那个 只与x有关的值 ,记 s x = ∑ i = 1 x a i s_x=\sum_{i=1}^{x}a_i sx=i=1xai ,那么可以从 y y y 转移到 x x x 的充要条件是 s x − s y ≥ s y − s g ( y ) s_x-s_y\ge s_y-s_{g(y)} sxsysysg(y) s x ≥ 2 s y − s g ( y ) s_x\ge 2s_y-s_{g(y)} sx2sysg(y)

y y y 越大越优,因为本质上 g ( x ) = y g(x)=y g(x)=y 。而且 s x s_x sx 是递增的!可以直接套 单调队列

对了, f f f 值好像不用存?因为 g ( x ) g(x) g(x) 已经全权代表了 f f f 。最后求答案的时候,直接模拟一遍。

代码

关于大整数的部分,可以直接用两个长整型接在一起。

对于带取模的 l o n g    l o n g \tt{long\; long} longlong 相乘,可以看。或者你一个__int128立刻完事儿。

#include 
#include
#include
using namespace std;inline int readint(){ int a = 0, f = 1; char c = getchar(); for(; c<'0' or c>'9'; c=getchar()) if(c == '-') f = -1; for(; '0'<=c and c<='9'; c=getchar()) a = (a<<3)+(a<<1)+(c^48); return a*f;}class BigInteger{ // 坑爹的大整数 // 答案在__int128范围内(即两个long long接在一起) const static long long Mod = (long long)1e18; long long head, tail;public: BigInteger(long long num = 0){ head = 0, tail = num; } BigInteger operator=(const long long num){ return *this = BigInteger(num); } BigInteger &operator+=(const BigInteger &that){ tail += that.tail; head += that.head; if(tail >= Mod) ++ head, tail -= Mod; return *this; } BigInteger sqr()const{ // when head equals to 0 BigInteger ppl; // result ppl.head = (long long)((long double)tail/Mod*tail); ppl.tail = (tail*tail-ppl.head*Mod+Mod)%Mod; return ppl; } void output(){ if(head){ printf("%lld",head); printf("%018lld",tail); } else printf("%lld",tail); }};const int MaxN = 40000000;long long s[MaxN+1]; int n;int b[MaxN+1];void input(){ n = readint(); int T = readint(); if(not T){ for(int i=1; i<=n; ++i) s[i] = s[i-1]+readint(); return ; } long long x = readint(), y = readint(), z = readint(); b[1] = readint(), b[2] = readint(); int m = readint(); const int Mod = (1<<30)-1; for(int i=3; i<=n; ++i) b[i] = (1ll*b[i-2]*y+1ll*b[i-1]*x+z)&Mod; for(int i=1,lastp=0,p,L,R; i<=m; ++i,lastp=p){ p = readint(), L = readint(), R = readint(); for(int j=lastp+1; j<=p; ++j) s[j] = s[j-1]+(b[j]%(R-L+1))+L; }}const int Mod = (1<<20)-1;// 在luogu题解中看到的循环队列优化 int q[Mod+1], head, tail, g[MaxN+1];# define pre(x) (((x)-1)&Mod)# define suf(x) (((x)+1)&Mod)# define val(x) (2*s[(x)]-s[g[(x)]])void solve(){ head = 0, tail = 0; // [head,tail) for(int i=1,now=0; i<=n; ++i){ while(head != tail and val(q[head]) <= s[i]) now = q[head], head = suf(head); g[i] = now; // now记录可行解(最大的一个) while(head != tail and val(q[pre(tail)]) >= val(i)) tail = pre(tail); // 单调队列 q[tail] = i, tail = suf(tail); } BigInteger ans = 0; for(int i=n; i; i=g[i]){ // printf("divide at: %d\n",i); BigInteger t = s[i]-s[g[i]]; ans += t.sqr(); } ans.output(); putchar('\n');}int main(){ input(), solve(); return 0;}
上一篇:Python3中// 和/区别
下一篇:自定义BootstrapTable扩展:分页跳转到指定页码

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2025年04月08日 18时05分48秒