题目大意
每个位置有两个值ai和bi。
给一个序列分成若干连续段,使得每个段内a值互不相同,代价是b的最大值。求最小代价。
DP
我们思考设fi表示给1~i分段的最小代价。
我们可以处理出ci表示一个最小的k使得[k,i]没有重复a。
这个随便用个桶就实现了吧。。
那么
Fi=mini−1j=ci−1(Fj+max(j+1,i))
这个dp是n^2的。
随机数据下该dp的复杂度
我们思考一个40分的暴力,这与我们每次从一个位置i往左,走多少步会出现区间内有相同数有关。
然后呢,对于每个i每次我们期望会在多少步后就结束循环?以下p代表权值范围。
假设i步就结算,那么花费了i步,概率是i-1步内未结束,在第i步时结束了。P(i-1步内未结束)=P(前i-1个互不相同)=(p/p)[(p-1)/p][(p-2)/p]……[(p-i)/p]=p!/[(p-i-1)!*p^(i-1)]
P(在第i步恰好结束)=P(i-1步内未结束)P(第i个与前i-1个中某一个相同)=p!/[(p-i-1)!*p^(i-1)](i-1)/p
对期望的贡献为P(在第i步恰好结束)*i
我们计算一下10^5时的期望,大约为400+。
因此该暴力算法的期望复杂度确实能通过全部的数据。
最后谴责,出题人出题如果要随机数据,应自己实践暴力来确保暴力不会过。不然在期望的情况下,一切均有可能。
正解
显然构造数据能卡掉40分暴力。
思考正解?可以想到根据后面那个max值分成若干段,那么我们维护了一个单调递减的形状,像单调栈一样,每一块可以维护其代表我们的j可选择的区间。
首先思考两个单调性。
ci<=ci+1
fi<=fi+1
单调性证明:显然。
我们维护单调队列,按照max值维护。每次将新的加入队尾不断踢出队尾,同时检验队首是否包含ci并不断踢出队首。
考虑一个块的贡献。对于不是队首的块,其不包含ci,因此可以随便选,由fi<=fi+1我们应该选择最左端的,因此f[最左端]+对应max值是该块贡献。
那么队首呢?队首肯定包含ci,选最左也不能选过ci,因此贡献为f[ci-1]+对应max值是队首贡献。
每次找到贡献最小的块就是fi,用数据结构维护。我懒就打了set。
#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10,inf=2000000050;
multiset<int> s;
int f[maxn],a[maxn],b[maxn],c[maxn],cnt[maxn],dl[maxn],L[maxn],g[maxn];
int i,j,k,l,t,n,m,head,tail;
int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int main(){
freopen("array.in","r",stdin);freopen("array.out","w",stdout);
n=read();
fo(i,1,n) a[i]=read(),b[i]=read();
j=1;
fo(i,1,n){
cnt[a[i]]++;
while (cnt[a[i]]>1){
cnt[a[j]]--;
j++;
}
c[i]=j;
}
fo(i,1,n) f[i]=inf;
head=1;
fo(i,1,n){
t=i-1;
while (head<=tail&&b[i]>=dl[tail]){
s.erase(s.find(g[tail]));
t=L[tail];
tail--;
}
dl[++tail]=b[i];
L[tail]=t;
while (head<tail&&L[head+1]-1<c[i]-1){
s.erase(s.find(g[head]));
head++;
}
if (head==tail){
g[tail]=f[c[i]-1]+dl[tail];
s.insert(g[tail]);
}
else{
g[tail]=f[L[tail]]+dl[tail];
s.insert(g[tail]);
s.erase(s.find(g[head]));
g[head]=f[c[i]-1]+dl[head];
s.insert(g[head]);
}
f[i]=*s.begin();
}
printf("%d\n",f[n]);
}