版权声明: https://blog.csdn.net/nwpu2017300135/article/details/81637678
http://acm.hdu.edu.cn/showproblem.php?pid=5009
题意:对一段字符串操作,一次操作可以操作一段连续的区间,开销为这段区间颜色种类数的平方。求使字符串每一个元素都被操作过至少一次的最小开销。
题解:DP的转移过程挺好想,就是(num(j,i)表示区间[i,j]的颜色种类数)但是朴素的写会超时。
算一下样例二会发现中间一大段只有3,2,4三种颜色,这一大段就可以直接跳过了,因为只要一直是这种三种颜色,肯定先搞前面的更优。这个跳过的过程可以用双向链表实现。这里用类似并查集的方式实现双向链表。
代码:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define maxn 50005
using namespace std;
int n;
int a[maxn],b[maxn],dp[maxn];
int pre[maxn],nxt[maxn];
int main()
{
int i,j,k;
int m;
map<int,int > mp;
pre[0]=-1;
while(~scanf("%d",&n)){
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
}
mp.clear();
memset(dp,0,sizeof(dp));
m=0;
a[0]=-1;
//缩点,同色连续相当于一个点
for(i=1;i<=n;i++){
if(a[i]!=a[i-1])b[++m]=a[i];
}
//初始化链表
for(i=1;i<=m;i++){
pre[i]=i-1;
nxt[i]=i+1;
}
for(i=1;i<=m;i++){
if(mp[b[i]]==0)mp[b[i]]=i;//没有出现过的颜色
else {
k=mp[b[i]];
nxt[pre[k]]=nxt[k];
pre[nxt[k]]=pre[k];
mp[b[i]]=i;
}//dp的时候从后向前遍历,出现过的颜色直接跳过
k=1;
dp[i]=i;
for(j=i-1;j!=-1;j=pre[j]){
if(k*k>m)break;//如果大于说明还不如直接一个点一个点的操作
int tmp=dp[j]+k*k;
if(tmp>m)break;
dp[i]=min(dp[i],tmp);
k++;
}
}
printf("%d\n",dp[m]);
}
//cout << "Hello world!" << endl;
return 0;
}