CF834D
题目链接:http://codeforces.com/contest/834/problem/D
题目大意:将一个有n个数的数列分成k段,每段的价值为该段中不同数字的个数,求k段的最大总价值。
解题思路:
思路来自叉姐 + GreenGrape
dp + segment trees.
dp不难想到。前 i 个数分成 j 段的最大价值:dp[i][j] = max( dp[i-1][k] + w(k+1,j), i-1 <= k < j). 但其实这样直接去搞的话分分钟TLE。
所以,我们需要使用线段树。详情请看代码,里面有个人的注释,请指教。
AC代码:
1 #include <cstdio> 2 #include <algorithm> 3 #include <iostream> 4 #include <cmath> 5 #include <cstring> 6 #include <vector> 7 #include <map> 8 #include <set> 9 #include <stack> 10 #include <string> 11 #include <queue> 12 13 using namespace std; 14 #define lson l , m , rt << 1 15 #define rson m + 1 , r , rt << 1 | 1 16 #define root 1 , N , 1 17 const int maxn=35000+5; 18 int a[maxn],dp[52][maxn],last[maxn]; 19 int pre[maxn]; 20 int tree[maxn<<2],lazy[maxn<<2]; 21 22 //***************************************************** 23 //这一部分其实就是走模板 24 void pushup(int rt){ 25 tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); 26 } 27 void pushdown(int rt){ 28 if(lazy[rt]){ 29 lazy[rt<<1]+=lazy[rt]; 30 lazy[rt<<1|1]+=lazy[rt]; 31 tree[rt<<1]+=lazy[rt]; 32 tree[rt<<1|1]+=lazy[rt]; 33 lazy[rt]=0; 34 } 35 } 36 void update(int L,int R,int c,int l,int r,int rt){ 37 if(L<=l&&r<=R){ 38 lazy[rt]+=c; 39 tree[rt]+=c; 40 return; 41 } 42 pushdown(rt); 43 int m=(l+r)>>1; 44 if(L<=m) update(L,R,c,lson); 45 if(m<R) update(L,R,c,rson); 46 pushup(rt); 47 } 48 int query(int L,int R,int l,int r,int rt){ 49 if(L<=l&&r<=R) 50 return tree[rt]; 51 pushdown(rt); 52 int m=(l+r)>>1; 53 int ret=0; 54 if(L<=m) ret=max(ret,query(L,R,lson)); 55 if(m<R) ret=max(ret,query(L,R,rson)); 56 return ret; 57 } 58 //******************************************************* 59 60 61 int main(){ 62 int n,k; 63 scanf("%d%d",&n,&k); 64 for(int i=1;i<=n;i++){ 65 scanf("%d",&a[i]); 66 pre[i]=last[a[i]];//pre[i]记录a[i]上一次出现的位置 67 last[a[i]]=i; 68 } 69 for(int i=1;i<=k;i++){ 70 memset(tree,0,sizeof(tree)); 71 memset(lazy,0,sizeof(lazy)); 72 for(int j=i-1;j<=n;j++) 73 update(j,j,dp[i-1][j],0,n,1);//把线段树各叶子结点的值初始化为dp[i-1][]的值,在dp[i][]这一维度上的操作其实就是在dp[i-1][]的基础上进行的。前面不能忘了把线段树的数据置0。 74 for(int j=i;j<=n;j++){ 75 //对于以x为结尾(pre[j] <= x <= j-1)的dp[i-1][x],将a[j]作为第 i 段的结尾可以使得dp[i-1][x]对应的dp[i][j]的值+1。 76 //故此时线段树维护的就是max(dp[i-1][k] + w(k+1,j), i-1 <= k < j)。w(k+1,j)在这个更新的过程中逐次加和累积。实在是精妙无比。 77 update(pre[j],j-1,1,0,n,1); 78 dp[i][j]=query(0,j,0,n,1); 79 } 80 } 81 int ans=0; 82 for(int j=k;j<=n;j++){ 83 if(dp[k][j]>ans) ans=dp[k][j]; 84 } 85 printf("%d\n",ans); 86 return 0; 87 }
“这些年我一直提醒自己一件事情,千万不要自己感动自己。大部分人看似的努力,不过是愚蠢导致的。什么熬夜看书到天亮,连续几天只睡几小时,多久没放假了,如果这些东西也值得夸耀,那么富士康流水线上任何一个人都比你努力多了。人难免天生有自怜的情绪,唯有时刻保持清醒,才能看清真正的价值在哪里。”