题意:有n个珠子,每个珠子有一个想要被涂成的颜色,每次可以选择一个区间进行涂色,花费为区间中不同颜色的珠子的数量的平方,求最小的花费将所有的珠子涂成目标颜色。
思路:可以假定涂色是从左到右一段一段涂的,不会选择相交的区间(不会得到任何好处)。那么用dp[i]表示涂完1~i的最小花费,dp[i] = min(dp[j] + differentcolor[j+1][i]^2),其中j < i。但是这样写复杂度很高,发现要增加的花费,即differentcolor[j+1][i] <= totalcolor^2,即最差也可以将n个珠子直接合并。那么其实这个状态只要向前找sqrt(n)个就行了。从左到右扫一遍,维护从位置j到当前位置i有k个不同的颜色的最小dp值的位置minp[i]。若lastp[c]表示上一个颜色c出现的位置,那么对于当前位置i,lastp[color[i]] + 1 ~ i的所有位置的不同颜色数都要加1,扫一遍minp,我们就知道哪些minp[c]要变成minp[c+1]了。吐槽一下数据,真是太水啦~
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn = 50000 + 10;
ll dp[maxn];
int color[maxn],minp[255];
int lastp[maxn];
map<int,int>mp;
int main()
{
// freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n;
while(~scanf("%d",&n))
{
mp.clear();
int cnt = 0;
for(int i = 1;i <= n;++i)
{
scanf("%d",&color[i]);
if(mp.find(color[i]) == mp.end())
{
mp[color[i]] = ++cnt;
color[i] = cnt;
}
else
color[i] = mp[color[i]];
}
int m = min(cnt,(int)sqrt(n) + 1);
memset(lastp,0,sizeof(lastp));
memset(minp,0,sizeof(minp));
dp[0] = 0;
for(int i = 1;i <= n;++i)
{
dp[i] = dp[i-1] + 1;
for(int j = m;j >= 1;--j)
{
if(minp[j] > lastp[color[i]])
minp[j] = 0;
if(minp[j-1] && minp[j-1] > lastp[color[i]])
{
if(minp[j] == 0)
minp[j] = minp[j-1];
else if(dp[minp[j]-1] > dp[minp[j-1]-1])
minp[j] = minp[j-1];
}
if(minp[j])
dp[i] = min(dp[i],dp[minp[j]-1] + (ll)j*j);
}
if(!minp[1])
minp[1] = i;
else if(dp[i-1] <= dp[minp[1]-1])
minp[1] = i;
lastp[color[i]] = i;
}
printf("%I64d\n",dp[n]);
}
return 0;
}