洛谷 P5665 [CSP-S2019] 劃分
連結:
題意:
給出 \(n\) 個整數 \(a_i\) ,你需要找到一些分界點 \(1 \leq k_1 \lt k_2 \lt \cdots \lt k_p \lt n\),使得
\(\sum\limits_{i=1}^{k_1} a_i \leq \sum\limits_{i=k_1+1}^{k_2} a_i \leq \cdots \leq \sum\limits_{i=k_p+1}^{n} a_i\)。
注意 \(p\) 可以為 \(0\) 且此時 \(k_0 = 0\)。
請你最小化
\((\sum\limits_{i=1}^{k_1} a_i)^2 + (\sum\limits_{i=k_1+1}^{k_2} a_i)^2 + \cdots + (\sum\limits_{i=k_p+1}^{n} a_i)^2\)
分析:
根據完全平方公式有:\((a+b)^2\geq a^2+b^2\)
所以分段比不分段更優。其次,對於一個數 \(x\),將他分到左邊和右邊會造成 \(2x*sum_{side}+x^2\) 的貢獻(\(sum\) 指兩區間和),又因為 \(sum_L\leq sum_R\) 所以儘量分到左邊更優。也就是說,最後一段和最小時,答案最優。(這個策略還是能猜出來,只是不敢確定)。於是就有後面的\(O(n^2)\) dp 了。
演算法:
首先維護一個字首和 \(sum\) (和上文 \(sum\) 不同)。設 \(d[i]\) 為 \(i\) 結尾,最後一段最小時上一段的結尾位置,於是有 \(d[i]=max\{j|sum[i]-sum[j]\geq sum[j]-sum[d[j]]\}\)
優化:
根據上述演算法可以寫出這樣的程式:
for(int i=1;i<=n;i++){ for(int j=i-1;j>=1;j--) if(sum[i]-sum[j]<sum[j]-sum[d[j]]) continue; d[i]=j; break; } int now=n; while(now){ int t=sum[now]-sum[d[now]]; ans+=t*t; now=d[now]; }
\(O(n^2)\) 複雜度可以通過64分的好成績,但是看到 \(2\leq n\leq4\times10^7\),這說明我們需要一個 \(O( n )\) 或實(hen)現(neng)良(ka)好(chang)的 \(O(n\log n)\)。
回顧演算法,發現判斷 \(j\) 是否合法時的柿子:
\(sum[i]-sum[j]\geq sum[j]-sum[d[j]]\)
可以繼續改寫:
\(2*sum[j]-sum[d[j]]\leq sum[i]\)
此時左邊只與 \(j\) 有關右邊只與 \(i\) 有關。設 \(A(j)=2*sum[j]-sum[d[j]]\) 。顯然 \(A(j)\) 越小,\(j\) 越可能成為合法答案,所以當存在 \(j_1<j_2\) 且 \(A(j_1)>A(j_2)\) 時,\(j_2\) 比 \(j_1\) 更優。
又有 \(sum[i]\lt sum[i+1]\) 所以當一個 \(j\) 滿足 \(A(j)\leq sum[i]\) 時,它也滿足 \(A(j)\leq sum[i+1]\)。
基於以上兩點我們可以維護出一個 \(A(j)\) 單調上升且 \(j\) 單調上升的單調佇列,每次轉移時找到最大的滿足 \(A(j)\leq sum[i]\) 的 \(j\),小於 \(j\) 的狀態可以捨棄,更新 \(d[i]\) 後將 \(A(i)\) 加入佇列尾並彈出 \(A(k)>A(i)\) 的狀態 \(k\),每個點最多進出1次,所以複雜度是 \(O(n)\)。
於是我們可以這樣維護 \(d[i]\):
for(int i=1;i<=n;i++){
while(head<tail&&A(q[head+1])<=sum[i])head++;
d[i]=q[head];
while(head<tail&&A(i)<A(q[tail]))tail--;
q[++tail]=i;
}
另外的,此題最後三個測試點相當毒瘤,輸入相當佔時間和空間,資料範圍會爆 long long
。我們不得不選擇高精(或考場上不敢寫的__int128),同時需要對空間和時間能夠精確把控,最好還是自己慢慢調,可以鍛鍊自己的程式碼能力。
程式碼:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define in read()
inline int read(){
int p=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
return p*f;
}
const int N=4e7+5;
ll sum[N];
int d[N],q[N],n,type,head,tail;
inline __int128 A(int i){return 2*sum[i]-sum[d[i]];}
//sub23~25
const int M=1e5+5;
const int mod=1ll<<30;
ll x,y,z,m;
int p[M],l[M],r[M];
ll b[N];
//
__int128 ans;
void print(__int128 x){
if(x==0){
cout<<0;
return ;
}
string res="";
while(x){
res+=x%10+'0';
x/=10;
}
reverse(res.begin(),res.end());
cout<<res;
}
signed main(){
n=in,type=in;
if(type==0)
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+in;
else{
x=in,y=in,z=in,b[1]=in,b[2]=in,m=in;
for(int i=1;i<=m;i++)
p[i]=in,l[i]=in,r[i]=in;
for(int i=3;i<=n;i++)
b[i]=((x*b[i-1]%mod+y*b[i-2]%mod)%mod+z)%mod;
int now=0;
for(int i=1;i<=n;i++){
if(i>p[now])now++;
sum[i]=sum[i-1]+b[i]%(r[now]-l[now]+1)+l[now];
}
}
for(int i=1;i<=n;i++){
while(head<tail&&A(q[head+1])<=sum[i])head++;
d[i]=q[head];
while(head<tail&&A(i)<A(q[tail]))tail--;
q[++tail]=i;
}
int now=n;
while(now){
__int128 t=sum[now]-sum[d[now]];
ans+=t*t;
now=d[now];
}
print(ans);
return 0;
}
可能是因其毒瘤的資料才成為了紫題