CodeForces833 B. The Bakery 线段树维护dp
题目链接:https://vjudge.net/problem/CodeForces-833B
题意:
给长度为n的数组a,和一个整数k
要求把数组分成连续的k段,每段的权值是该段中不同数的个数,
输出最大权值和。
数据范围:n<=35000,k<=min(n,50),1<=a(i)<=n
题解:
很容易想到暴力dp的方式
dp[i][j]:前1-i个数分成了j个段
那么dp[i][j]=max(dp[k][j-1]+val[k+1][i]) //val[i][j]表示区间i-j中不同数的个数
这个方法的复杂度就是O(n^2*k),很显然不可行
那么我们可以把找max(dp[k][j-1]+val[k+1][i])这个过程使用线段树优化掉
我们可以从1-n的数分成j段建立一颗线段树,也就是对dp[x][j](1<=x<=n)建一颗线段树维护这n个数的最大值
然后对dp[i][j]的求解可以从维护dp[x][j-1]这一颗树中查找区间【1,(i-1)】的最大值
但是有一点不对,因为dp[i][j]由dp[k][j-1]和val[k+1][i]两部分构成,所以只有dp[k][j-1]最大并不一定可以得到
dp[i][j]最大,所以我们就要想办法处理一下维护dp[x][j]的线段树,让线段树维护这两部分的和
对于下面的一组数(下标从1开始)
7 8 1 7
第二个7的有效区域是[2,4],那么我们可以在原有维护dp[x][j]的线段树基础上,线段树在[2,4]这个区间的值都加1
这样的话就相当于让线段树维护了两部分的和
/* 题意: 给长度为n的数组a,和一个整数k 要求把数组分成连续的k段,每段的权值是该段中不同数的个数, 输出最大权值和。 数据范围:n<=35000,k<=min(n,50),1<=a(i)<=n 题解: 很容易想到暴力dp的方式 dp[i][j]:前1-i个数分成了j个段 那么dp[i][j]=max(dp[k][j-1]+val[k+1][i]) //val[i][j]表示区间i-j中不同数的个数 这个方法的复杂度就是O(n^2*k),很显然不可行 那么我们可以把找max(dp[k][j-1]+val[k+1][i])这个过程使用线段树优化掉 我们可以从1-n的数分成j段建立一颗线段树,也就是对dp[x][j](1<=x<=n)建一颗线段树维护这n个数的最大值 然后对dp[i][j]的求解可以从维护dp[x][j-1]这一颗树中查找区间【1,(i-1)】的最大值 但是有一点不对,因为dp[i][j]由dp[k][j-1]和val[k+1][i]两部分构成,所以只有dp[k][j-1]最大并不一定可以得到 dp[i][j]最大,所以我们就要想办法处理一下维护dp[x][j]的线段树,让线段树维护这两部分的和 对于下面的一组数(下标从1开始) 7 8 1 7 第二个7的有效区域是[2,4],那么我们可以在原有维护dp[x][j]的线段树基础上,线段树在[2,4]这个区间的值都加1 这样的话就相当于让线段树维护了两部分的和 */ #include <cstdio> #include <cstring> #include <iostream> #include<algorithm> #include <queue> #include <map> using namespace std; typedef long long ll; const int maxn=5e4+10; const int mod=1000000007; const int INF=0x3f3f3f3f; const long long ll_INF=0x3f3f3f3f3f3f3f3fll; int tree[maxn<<2],dp[maxn][55],v[maxn],pre[maxn],mark[maxn],lazy[maxn<<2]; void push_up(int rt) { tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); } void build(int rt,int L,int R,int x) { tree[rt]=lazy[rt]=0; if(L==R) { //对于dp[j][i]的值由dp[k][i]+val[k+1][j]得到,且要保证k<j,所以用dp[L-1][x-1]来给第L位置赋值 tree[rt]=dp[L-1][x-1]; //给n个节点赋初始值 return; } int mid=(L+R)>>1; build(rt<<1,L,mid,x); build(rt<<1|1,mid+1,R,x); push_up(rt); } void push_down(int rt) { if(lazy[rt]) { lazy[rt<<1]+=lazy[rt]; //上一个节点的lazy值保存的是它子节点的偏移量 lazy[rt<<1|1]+=lazy[rt]; tree[rt<<1]+=lazy[rt]; tree[rt<<1|1]+=lazy[rt]; lazy[rt]=0; } } void update(int rt,int L,int R,int LL,int RR) { if(LL<=L && RR>=R) { lazy[rt]++; tree[rt]++; return ; } push_down(rt); int mid=(L+R)/2; if(LL<=mid)update(rt<<1,L,mid,LL,RR); if(RR>mid)update(rt<<1|1,mid+1,R,LL,RR); push_up(rt); } int query(int rt,int L,int R,int LL,int RR) { if(LL<=L && RR>=R) { return tree[rt]; } push_down(rt); int mid=(L+R)>>1,ans=0; if(LL<=mid) ans=max(ans,query(rt<<1,L,mid,LL,RR)); if(RR>mid) ans=max(ans,query(rt<<1|1,mid+1,R,LL,RR)); return ans; } int main() { int n,k; scanf("%d%d",&n,&k); for(int i=1; i<=n; ++i) scanf("%d",&v[i]); for(int i=1; i<=n; ++i) { pre[i]=mark[v[i]]+1; mark[v[i]]=i; } for(int i=1; i<=k; ++i) { build(1,1,n,i); for(int j=1; j<=n; ++j) { update(1,1,n,pre[j],j); dp[j][i]=query(1,1,n,1,j); } } printf("%d\n",dp[n][k]); return 0; }