传送门 to luogu
传送门 to nowcoder
枚举每个划分点。 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=0minx−1f(z,x)[r=x+1∑yar≥r=z+1∑xar]
直接这样实现,复杂度就会是 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| ∣a−b∣<∣c−d∣,那么 a 2 + b 2 < c 2 + d 2 a^2+b^2
证明
考虑 a , b a,b a,b 和 a − x , b + x a-x,b+x a−x,b+x (假设 0 ≤ x ≤ a ≤ b 0\le x\le a\le b 0≤x≤a≤b )。
直接暴力计算右边那一组的贡献,为 ( 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) (a−x)2+(b+x)2=a2+b2+2x(x+b−a) 。
由于 0 ≤ x ≤ a ≤ b 0\le x\le a\le b 0≤x≤a≤b ,所以 2 x ( x + b − a ) > 0 2x(x+b-a)>0 2x(x+b−a)>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
证明
譬如上图,两个红色的区间不满足上面的条件。(黑色的区间只是表示 r 2 < k r_2
由于右端点小于 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
但是,方案三比方案二还要差,所以 上面的方案劣于下面的方案!
知道这个性质,如何优化我们的 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)} sx−sy≥sy−sg(y) 即 s x ≥ 2 s y − s g ( y ) s_x\ge 2s_y-s_{g(y)} sx≥2sy−sg(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;
}