首先把具有相同颜色的点缩成一个点,即数据离散化。
然后使用dp[i]表示涂满前i个点的最小代价。对于第i+1个点,有两种情况:
1)自己单独涂,即dp[i+1] = dp[i] + 1
2)从第k个节点之后(不包括k)到第i+1个节点一次涂完,且一起涂的节点共有num种颜色,即dp[i+1] = dp[k] + num * num
从而可以得到状态转移方程dp[i+1] = min(dp[i], dp[k] + num * num)
但是如果从后往前遍历每一个k,会超时。
因此我们可以使用双向链表来把每种颜色最后出现的位置穿起来。对于每一个新加入的点,如果该点颜色没出现过,那么把它加入到双向链表的结尾。如果该点出现过,把它最后出现的位置从双向链表中删除,并把最新的位置加入到双向链表结尾。
需要注意的是要建立一个头节点,使得第一个节点不会因为后面出现了同样的颜色而被删除,从而无法计算从头到尾一次性涂完的情况。
第二个要注意的是如果num * num 已经大于了每个节点单独涂的代价 i,那么就没有必要再往前查找了。
代码如下:
1 #define MAXN 50005 2 #include <stdlib.h> 3 #include <iostream> 4 #include <stdio.h> 5 #include <map> 6 #include <limits.h> 7 8 using namespace std; 9 int arr[MAXN];//输入 10 int l[MAXN];//记录前一个最后出现的颜色的位置 11 int r[MAXN];//记录后一个最后出现颜色的位置 12 int dp[MAXN];//dp[i]表示涂到第i个节点最小的代价 13 int m;//离散化后数组的长度 14 15 void solve() 16 { 17 map<int, int> exist;//map 存储出当前现过得颜色中,最后出现的位置 18 int last = 1;//双向链表尾 19 l[0] = -1;//头节点 20 r[0] = 1;//头节点 21 l[1] = 0; 22 exist[arr[1]] = 1; 23 dp[0] = 0; 24 dp[1] = 1; 25 26 for( int i = 2 ; i < m ; i++ ) 27 { 28 if( exist.count(arr[i]) == 0 ) 29 { 30 r[last] = i;//添加到尾部 31 l[i] = last; 32 last = i; 33 exist[arr[i]] = i; 34 } 35 else 36 { 37 int tmp = exist[arr[i]]; 38 r[l[tmp]] = r[tmp];//删除tmp 39 l[r[tmp]] = l[tmp];//删除tmp 40 r[last] = i; 41 l[i] = last; 42 last = i; 43 exist[arr[i]] = i; 44 } 45 46 int k = last; 47 dp[i] = dp[i-1]+1; 48 int num = 1; 49 while( l[k] >= 0 ) 50 { 51 dp[i] = min(dp[i], dp[l[k]] + num*num); 52 num++; 53 k = l[k]; 54 if( num * num > i )//剪枝 55 { 56 break; 57 } 58 } 59 } 60 printf("%d\n", dp[m-1]); 61 } 62 63 int main(int argc, char *argv[]) 64 { 65 int n; 66 while(scanf("%d", &n) != EOF) 67 { 68 int a; 69 m = 1; 70 for( int i = 1 ; i <= n ; i++ )//从1开始,位置0是头节点 71 { 72 scanf("%d", &a); 73 if( i == 1 ) 74 { 75 arr[m++] = a; 76 } 77 else if( arr[m-1] != a )//合并相同颜色的节点,离散化 78 { 79 arr[m++] = a; 80 } 81 } 82 solve(); 83 } 84 return 0; 85 }
时间: 2024-10-14 17:38:56