codeforces #426 div2(The Bakery)834d

题目链接
本菜感觉这个题目真是难,看了好久的题解才懂起,大致题意就是给一个数组,把数组连续的分为k部分,每一部分的值等于这部分不同的数字的个数,要让所有的值加起来最大,问怎样分。


首先我们来看一下dp:
dp[i][j] 表示前i个数字分成j组的的最优方案,那么很容易就能够得出一个状态转移公式
dp[i][j]=max(dp[t][j1]+F(t+1,i),j1<=t<i) 其中 F(x,y) 表示x到y的分为一个区间的价值,但是复杂度完太高,于是这里用线段树来处理一下,最外层来枚举j,然后枚举i的时候有线段树来优化,怎么弄呢?
首先把线段树用 dp[x][j1],(0<=x<=n)] 来初始化,那么线段树就是维护的整个区间的最大值,然后更新当前状态的时候,就从上一个和当前数字相等的数位置t 到 i-1区间的值都加1 然后当前最优的值就是0到i-1 区间的最大值,因为每次加一相当于从那个位置往后的区间多一个不同的数也就是当前的第j个区间的值,然后线段树查询就能过,最后答案就是dp[n][k]

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
#define LL long long
const int N = 35005;
struct TreeNode{
    int ma;
    int cnt;
}tree[N<<2];
int n;
void PushUp(int now){
    tree[now].ma = max(tree[now<<1].ma,tree[now<<1|1].ma);
}
void PushDown(int now){
    if(tree[now].cnt!=0)
    {
        int lson = now<<1;
        int rson = now<<1|1;
            tree[lson].cnt += tree[now].cnt;
            tree[lson].ma += tree[now].cnt;

            tree[rson].cnt += tree[now].cnt;
            tree[rson].ma += tree[now].cnt;
        tree[now].cnt = 0;
    }

}
void Update(int ul,int ur,int add,int l,int r,int now)
{
    if(ul<=l && r <= ur)
    {
        tree[now].cnt += add;
        tree[now].ma += add;
        return;
    }
    PushDown(now);
    int mid = (l+r)/2;
    if(ul<=mid)
        Update(ul,ur,add,l,mid,now<<1);
    if(ur>mid)
        Update(ul,ur,add,mid+1,r,now<<1|1);
    PushUp(now);

}
int Query(int ql,int qr,int l,int r,int now)
{
    if(ql <= l && r <= qr)
    {
        return tree[now].ma;
    }
    PushDown(now);
    int mid = (l+r)/2;
    int a1,a2;
    a1 = a2 = -9999999;
    if(ql<=mid)
    {
        a1 = Query(ql,qr,l,mid,now<<1);
    }
    if(qr>mid)
    {
        a2 = Query(ql,qr,mid+1,r,now<<1|1);
    }
    return max(a2,a1);
}
int dat[35005];
int dp[35005][55];
int last[35005];
int now[35005];
int main()
{
    int k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        clr(last);
        clr(now);
        for(int i = 1;i<=n;i++)
        {
            scanf("%d",&dat[i]);
            last[i] = now[dat[i]];
            now[dat[i]] = i;
        }
        clr(dp);
        clr(tree);
        for(int i = 1;i<=k;i++)
        {
            clr(tree);
            for(int j = 1;j<=n;j++)
            {
                Update(j,j,dp[j][i-1],0,n,1);
            }

            for(int j = i;j<=n;j++)
            {
                Update(max(last[j],i-1),j-1,1,0,n,1);
               /* if(i==3)
            {
                for(int ii = 1;ii<=n;ii++)
                {
                    cout << Query(ii,ii,0,n,1) << " ";
                }
                cout << endl;
            }*/
                //if(i==3 && j)cout << j<<"kk" << Query(2,7,0,n,1) << endl;
                dp[j][i] = Query(i-1,j-1,0,n,1);

            }
        }

        printf("%d\n",dp[n][k]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值