hdu5009 Paint Pearls (DP+模拟链表)

http://acm.hdu.edu.cn/showproblem.php?pid=5009

2014网络赛 西安 比较难的题

Paint Pearls

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1951    Accepted Submission(s): 631

Problem Description
Lee has a string of n pearls. In the beginning, all the pearls have
no color. He plans to color the pearls to make it more fascinating. He
drew his ideal pattern of the string on a paper and asks for your help.

In each operation, he selects some continuous pearls and all these pearls will be painted to their target colors. When he paints a string which has k different target colors, Lee will cost k2 points.

Now, Lee wants to cost as few as possible to get his ideal string. You should tell him the minimal cost.

 
Input
There are multiple test cases. Please process till EOF.

For each test case, the first line contains an integer n(1 ≤ n ≤ 5×104), indicating the number of pearls. The second line contains a1,a2,...,an (1 ≤ ai ≤ 109) indicating the target color of each pearl.

 
Output
For each test case, output the minimal cost in a line.
 
Sample Input
3
1 3 3
10
3 4 2 4 4 2 4 3 2 2
 
Sample Output
2
7
 
Source
 
Recommend
hujie   |   We have carefully selected several similar problems for you:  5041 5040 5039 5038 5036

题意:

给出n个数,代表一排珍珠要涂的颜色,刚开始全都没有颜色。可以对连续若干个珍珠发动连涂,代价是这若干珍珠的不同颜色数的平方。涂完这串珍珠,求最小代价和。

题解:

O(n*根号n)的DP,还需要一些超碉方法来处理。

先进行一些小处理,最多5W珍珠,颜色其实也最多5W种,它给的颜色有10^9种,我先用map处理一下变成5w种。

然后连续若干个相同颜色的其实可以合并成一个。经过上面这两个处理,得到了新的珍珠数组。然后才是难点。

先看,最多5W个珍珠,233^2>50000,也就是233连涂就已经超过5W个分别单独涂的代价了。

动态规划,dp[i]表示涂完第i个珍珠所需的代价。

dp[i] = max( dp[i] , dp[k] + j*j)

j为j之后一直到i的不同颜色数。不同颜色数 j 最多为根号n,所以DP是O(n*根号n)的。

i从1到n,j从1到j*j>=i(因为1~i分别单独涂的代价是i)。

关键是如何求每个j对应的k。

假设已知对 i 的每个 j 对应的k,则我们可以求出对 i+1 的每个 j 对应的 k。比较容易想到。具体是这样:

设i的j对应的k为k(i,j)。

b[]为珍珠数组,

若b[i+1]为新出现的颜色,则

  k( i+1 , j+1 ) = k( i , j )

  k( i+1 , 1 ) = i + 1

若b[i+1]为以前就有的颜色,我们用一个last[]数组存每个颜色最后出现的位置,则

对 ( k( i , j ) > last[ b[i+1] ] ) 的j 有 k( i+1 , j+1 ) = k( i , j )

对 ( k( i , j ) < last[ b[i+1] ] ) 的j 则没有变化。

k( i+1 , 1 ) = i + 1

这样我们就能从i 推到 i+1了。

可以发现其中的操作都是连续一堆数向后移动一个下标,如果数组直接搞会TLE。我是用了模拟链表来搞,新颜色出现就直接在后面加,出现旧颜色就把那个分界点删掉,然后在后面加。

代码:

 //#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
#define usll unsigned ll
#define mz(array) memset(array, 0, sizeof(array))
#define minf(array) memset(array, 0x3f, sizeof(array))
#define REP(i,n) for(i=0;i<(n);i++)
#define FOR(i,x,n) for(i=(x);i<=(n);i++)
#define RD(x) scanf("%d",&x)
#define RD2(x,y) scanf("%d%d",&x,&y)
#define RD3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define WN(x) printf("%d\n",x);
#define RE freopen("xorin.txt","r",stdin)
#define WE freopen("xorin.txt","w",stdout)
#define mp make_pair
#define pb push_back
const int maxn=;
const int maxm=;
int b[maxn],bn;
int cc[maxm+];
int dp[maxn];
int d[maxn],dn,dq;///d[j]存着某个位置,存了dn个在1~dn,最后一个在dq;
int L[maxn],R[maxn];///L[d[dn]]存着第二个不同的的d[]的下标,再L一下是第三个的
int last[maxn];///last[x]表示x上次出现的位置的d[]的下标
int farm() {
int i,j,k;
// FOR(j,1,bn)printf("%3d",b[j]);
// puts("");
mz(last);
L[]=-;
d[]=;
b[]=;
dn=;
dq=;
d[]=;
L[]=;
R[]=;
last[b[]]=;
FOR(i,,bn) {
dp[i]=i;
j=;
for(k=L[dq]; k!=-; k=L[k]) {
if(cc[j]>=dp[i])break;
//printf("dp[%d]=min(%d , %d + %d)\n",i,dp[i], dp[d[k]],cc[j]);
dp[i]=min(dp[i] , dp[d[k]] + cc[j]);
j++;
}
if(last[b[i+]]==) {
dn++;
R[dq]=dn;
L[dn]=dq;
R[dn]=;
dq=dn;
d[dn]=i+; } else {
j=last[b[i+]];
L[R[j]]=L[j];
R[L[j]]=R[j];
L[j]=dq;
R[j]=;
R[dq]=j;
dq=j;
d[dq]=i+;
}
last[b[i+]]=dq;
}
return dp[bn];
} int a[maxn];
map<int,int>S;
int main() {
int n,i,t;
int x;
FOR(i,,maxm) {
cc[i]=i*i;
} while(RD(n)!=EOF) {
S.clear();
int cnt=;
bn=;
FOR(i,,n) {
RD(x);
int t=S[x];
if(t==) S[x]=t=++cnt;
if(b[bn]!=t) b[++bn]=t;
}
printf("%d\n",farm());
}
return ;
}

有点难,比赛时看了半天不懂,比完了搞了半天也不太懂……搞出来了以后发现思路应该还是挺简单的……

网上找到很多不同的方法,kuangbin菊苣说这个很水,他的并查集的方法我看不太懂……

上一篇:boost::function的用法


下一篇:C#/.Net判断是否为周末/节假日