【题解】洛谷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;
}
posted @ 2024-11-25 09:28  sad_lin  阅读(19)  评论(0)    收藏  举报