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;
}

 

 

 

posted @ 2020-11-14 17:11  kongbursi  阅读(79)  评论(0编辑  收藏  举报