神题一道
变量异常复杂,请在结合程序及注释的情况下观看。
根据题意不难看出,在选择的一段区间内,最多有
n
\sqrt{n}
n种不同的食物。
据此设计dp数组f[i]表示i时的最小代价。
设计辅助数组pos[j]表示最多选j种食物时区间左端点位置。
思考:遍历到i时,使区间食物种数不变应满足什么条件?
得出:若上一个该种食物同样在此期间内,食物的种数便不必增加。
同理,若一个食物,其下一个相同食物在区间外,那么它就是这个区间内最后一个该种食物。设计数组cnt[j]表示从pos[j]到i的食物总数。当 cnt[j]>jcnt[j]>j 时,需弹出一种食物。
方法如上文所述,若当前pos[j]的下一个同种仍在该区间内,pos[j]便不断增加,直到现第一个超越区间的数,然后将其弹掉就可以了。
转移方程就是
f
[
i
]
=
m
i
n
(
f
[
p
o
s
[
j
]
−
1
]
+
j
∗
j
,
f
[
i
]
)
f[i]=min(f[pos[j]-1]+j*j,f[i])
f[i]=min(f[pos[j]−1]+j∗j,f[i]) 。
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int N=5e4;
int nex[N],pos[N],pre[N],last[N],n,m,dp[N],cnt[N];
int main(){
int i,j,c,t;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%d",&c);
pre[i]=last[c]; //pre:与编号为i的奶牛要求同一食物的前一个奶牛的位置
nex[last[c]]=i;
last[c]=i; //last:要求为c食物的奶牛最后一次出现的位置
nex[i]=n+1;dp[i]=N; //nex:与编号为i的奶牛要求同一食物的下一个奶牛的位置
}
t=sqrt(n); //最多可以满足在一个区间内有t个不同的食物
for(i=1;i<=t;i++) pos[i]=1; //pos:区间中不同食物小于等于j的最远位置
for(i=1;i<=n;i++){
for(j=1;j<=t;j++){
if(pre[i]<pos[j]) cnt[j]++;
//cnt[j]代表从pos[j]到i的食物种数
//如果与i奶牛要求相同的奶牛位置超过了最远位置-->超过了区间-->该区间中新增一个全新的食物
if(cnt[j]>j){ //当前区间不同食物数大于j
cnt[j]--; //撤销
while(nex[pos[j]]<i) pos[j]++;
//如果nex[pos[j]]<i,说明在pos[j]这个位置的食物在区间中出现了不止一次,则删除它后不同个数仍然不变
//因此要不断右移,直到nex[pos[j]]>i,此时删除它后不同个数减一(nex[pos[j]]不可能等于i)
pos[j]++;//抛弃它
}
dp[i]=min(dp[pos[j]-1]+j*j,dp[i]);//统计
}
}
printf("%d",dp[n]);
return 0;
}