【Coderforces 833 B. The Bakery】 线段树+dp

本文介绍了如何解决Codeforces 833B问题,即如何将n个数分成k部分以最大化每部分中不同数的个数。通过动态规划dp[i][k]来表示以i结尾且已划分成k部分的最优解,原解法复杂度为O(n^2),会超时。为优化,引入线段树进行O(1)查询,并利用pre数组更新不同数的贡献,将复杂度降低,确保在限制时间内完成计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Codeforces 833B
题意:把n个数分成k部分,使得每部分价值之和最大。每部分的价值为不同数的个数

我们首先知道这题很容易用 d p [ i ] [ k ] &ThinSpace;&ThinSpace; 想到是以  i &ThinSpace;&ThinSpace; 结尾 前面已经有 k 部分的方案数 那么 d p [ i ] [ k ] &ThinSpace;&ThinSpace; = &ThinSpace;&ThinSpace; max ⁡ ( d p [ i ] [ k − 1 ] , d p [ j ] [ k − 1 ] + n u m [ j + 1 ] [ i ] ) 这样直接跑是 O ( n 2 m ) 的 会 T L E &ThinSpace;&ThinSpace; 如果我们把取 max ⁡ x &ThinSpace;&ThinSpace; 用一个数组记录下来 那么每次决策就是 O ( 1 ) 的转移 但是这不是类似层次的东西 所以我们可以建一颗线段树 叶子节点的值是  d p [ j ] [ k − 1 ] &ThinSpace;&ThinSpace; + &ThinSpace;&ThinSpace; n u m [ j + 1 ] [ i ] 可是怎么更新 n u m 呢?对于这种不同数的问题 我们很容易想到 p r e 数组 我们不考虑单个的 n u m 我们考虑全局 n u m &ThinSpace;&ThinSpace; 每个数字对一段区间的贡献就是  p r e [ i ] , i − 1 这段会加 1 于是更新部分就结束了 就可以开始 q u e r y &ThinSpace;&ThinSpace; 了 因为  k − 1 &lt; = j &ThinSpace;&ThinSpace; &lt; &ThinSpace;&ThinSpace; i &ThinSpace;&ThinSpace; 所以 j 范围也有了 i 每次要从 k 开始 不然前面不能凑齐 k − 1个 \text{我们首先知道这题很容易用}dp\left[ i \right] \left[ k \right] \,\,\text{想到是以 }i\,\,\text{结尾 前面已经有}k\text{部分的方案数} \\ \text{那么}dp\left[ i \right] \left[ k \right] \,\,=\,\,\max \left( dp\left[ i \right] \left[ k-1 \right] ,dp\left[ j \right] \left[ k-1 \right] +num\left[ j+1 \right] \left[ i \right] \right) \\ \text{这样直接跑是}O\left( n^2m \right) \text{的 会}TLE\,\,\text{如果我们把取}\max x\,\,\text{用一个数组记录下来} \\ \text{那么每次决策就是}O\left( 1 \right) \text{的转移 但是这不是类似层次的东西} \\ \text{所以我们可以建一颗线段树} \\ \text{叶子节点的值是 }dp\left[ j \right] \left[ k-1 \right] \,\,+\,\,num\left[ j+1 \right] \left[ i \right] \\ \text{可是怎么更新}num\text{呢?对于这种不同数的问题} \\ \text{我们很容易想到}pre\text{数组 我们不考虑单个的}num \\ \text{我们考虑全局}num\,\,\text{每个数字对一段区间的贡献就是 }pre\left[ i \right] ,i-\text{1 这段会加}1 \\ \text{于是更新部分就结束了 就可以开始}query\,\,\text{了 因为 }k-1&lt;=j\,\,&lt;\,\,i\,\,\text{所以}j\text{范围也有了} \\ i\text{每次要从}k\text{开始 不然前面不能凑齐}k-\text{1个} \\ 我们首先知道这题很容易用dp[i][k]想到是以 i结尾 前面已经有k部分的方案数那么dp[i][k]=max(dp[i][k1],dp[j][k1]+num[j+1][i])这样直接跑是O(n2m) TLE如果我们把取maxx用一个数组记录下来那么每次决策就是O(1)的转移 但是这不是类似层次的东西所以我们可以建一颗线段树叶子节点的值是 dp[j][k1]+num[j+1][i]可是怎么更新num呢?对于这种不同数的问题我们很容易想到pre数组 我们不考虑单个的num我们考虑全局num每个数字对一段区间的贡献就是 pre[i],i这段会加1于是更新部分就结束了 就可以开始query 因为 k1<=j<i所以j范围也有了i每次要从k开始 不然前面不能凑齐k1

/*
    if you can't see the repay
    Why not just work step by step
    rubbish is relaxed
    to ljq
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;

#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))
#define lc (rt<<1)
#define rc (rt<<11)
#define mid ((l+r)>>1)

typedef pair<int,int> pll;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int _inf = 0xc0c0c0c0;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll _INF = 0xc0c0c0c0c0c0c0c0;
const ll mod =  (int)1e9+7;

ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){int ans=1;while(b){if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){return ksm(a,mod-2,mod);}
void exgcd(ll a,ll b,ll &x,ll &y,ll &d){if(!b) {d = a;x = 1;y=0;}else{exgcd(b,a%b,y,x,d);y-=x*(a/b);}}//printf("%lld*a + %lld*b = %lld\n", x, y, d);
const int MAX_N = 35025;
int maxx[MAX_N<<2],col[MAX_N<<2],pre[MAX_N],last[MAX_N],arr[MAX_N],dp[MAX_N][55];
set<int > st;
void up(int rt)
{
    maxx[rt] = max(maxx[rt<<1],maxx[rt<<1|1]);
}
void down(int rt,int l,int r)
{
    if(col[rt])
    {
        col[rt<<1] += col[rt];
        col[rt<<1|1] += col[rt];
        maxx[rt<<1] += col[rt];
        maxx[rt<<1|1] += col[rt];
        col[rt] = 0;
    }
}
void build(int rt,int l,int r,int v)
{
    col[rt] = maxx[rt] = 0;
    if(l==r)
    {
        maxx[rt] = dp[l][v];
        return;
    }
    build(rt<<1,l,mid,v);
    build(rt<<1|1,mid+1,r,v);
    up(rt);
}
void update(int rt,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)
    {
        col[rt]++;
        maxx[rt]++;
        return;
    }
    down(rt,l,r);
    if(x<=mid) update(rt<<1,l,mid,x,y);
    if(mid<y) update(rt<<1|1,mid+1,r,x,y);
    up(rt);
}
int query(int rt,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)
    {
        return maxx[rt];
    }
    down(rt,l,r);
    if(y<=mid) return query(rt<<1,l,mid,x,y);
    else if(x>mid) return query(rt<<1|1,mid+1,r,x,y);
    else return max(query(rt<<1,l,mid,x,y),query(rt<<1|1,mid+1,r,x,y));
}
int main()
{
    //ios::sync_with_stdio(false);
    //freopen("a.txt","r",stdin);
    //freopen("b.txt","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=n;++i) scanf("%d",&arr[i]),pre[i] = last[arr[i]],last[arr[i]] = i,st.insert(arr[i]),dp[i][1] = st.size();
    for(int k = 2;k<=m;++k)
    {
        build(1,1,n,k-1);
        for(int i = k;i<=n;++i)
        {
            update(1,1,n,pre[i],i-1);
            dp[i][k] = max(dp[i][k-1],query(1,1,n,k-1,i-1));
        }
    }
    printf("%d\n",dp[n][m]);
    //fclose(stdin);
    //fclose(stdout);
    //cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值