【题解】洛谷P11311、P2943: 漫长的小纸带、Cleaning Up G
赛时不会去想 dp,感觉没法转移,然后去写了贪心,然后直接假掉唐完了。
为什么贪心不能做,因为多个数的话还是可能被减,\(3\) 个数长度为 \(11\) 就可以变成 \(9\),非常划算,好像很显然,但是为什么我赛时写了只会有长度 \(2\) 的区间唐完了。
考虑 dp,设 \(f_i\) 表示 \(1-i\) 的最小代价,枚举上一次分段的位置 \(j\),有代价转移 \(f_i=\min_{j=1}^{i} f_{j-1}+g(i,j)^2\),其中 \(g(i,j)\) 为 \(i\) 到 \(j\) 之间不同的数的个数,这样做的复杂度为 \(O(n^2)\)。
考虑优化,其中最耗时的是求区间不同的数的个数, \(i\) 是上界,所有 \(g(i,j)\le \sqrt{i}\) 时才有意义,所有我们可以枚举区间不同的数的个数,对于 \(j\) 有 \(pos_j\) 表示 \(i-pos_j\) 有 \(j\) 个不同的数个数的最右端点,所有我们转移方程可以变化了 \(f_i=\min_{j=1}^{\sqrt{i}} f_{pos_j-1}+j^2\),这样复杂度 \(O(n\sqrt{n})\)。
现在我们要维护右端点了,每加入一个数,\(g(i,j)\) 单调不降,我们就可以移动指针,这里是类似莫队的思想,不断右移指针直到合法,总复杂度 \(O(n)\),一共有 \(sqrt{n}\) 个指针,所以维护的总复杂度为 \(O(n\sqrt{n})\)。
因为移动指针要存桶,而数据范围很大要先离散化一下,代码全部复杂度为 \(O(n+n\log n+n+n\sqrt{n}+n\sqrt{n})\)。
第一题的复杂度为 \(2e5+2e5×17+2e5+2e5×450 +2e5×450 =183,800,000\)。
范围大的一维放前面,连续内存访问常数小。
#include <bits/stdc++.h>
#define ll long long
//#define int ll
//#define ls p<<1
//#define rs p<<1|1
#define re register
const int N=2e5+10;
//const int mod=998244353;
using namespace std;
int n;
int a[N];
ll f[N];
int cnt[450];
int b[N][450];
int pos[450];
int po[450];
int c[N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n;
for(re int i=1;i<=n;i++){
cin>>a[i];
c[i]=a[i];
f[i]=1e18;
}
sort(c+1,c+1+n);
int tot=unique(c+1,c+n+1)-c-1;
for(re int i=1;i<=n;i++){
a[i]=lower_bound(c+1,c+tot+1,a[i])-c;
}
f[0]=0;
for(re int i=1;i<=448;i++){
po[i]=i*i;
}
for(re int i=1;i<=n;i++){
for(re int j=1;j<=448;j++){
++b[a[i]][j];
if(b[a[i]][j]==1){
++cnt[j];
if(cnt[j]>j){
while(--b[a[pos[j]]][j]!=0) ++pos[j];
++pos[j];
cnt[j]=j;
}
}
if(cnt[j]==j){
f[i]=min(f[i],f[pos[j]-1]+po[j]);
}
}
}
cout<<f[n];
return 0;
}