Codeforces 833B 题解(DP+线段树)
题面
传送门:http://codeforces.com/problemset/problem/833/B
B. The Bakery
time limit per test2.5 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
Some time ago Slastyona the Sweetmaid decided to open her own bakery! She bought required ingredients and a wonder-oven which can bake several types of cakes, and opened the bakery.
Soon the expenses started to overcome the income, so Slastyona decided to study the sweets market. She learned it’s profitable to pack cakes in boxes, and that the more distinct cake types a box contains (let’s denote this number as the value of the box), the higher price it has.
She needs to change the production technology! The problem is that the oven chooses the cake types on its own and Slastyona can’t affect it. However, she knows the types and order of n cakes the oven is going to bake today. Slastyona has to pack exactly k boxes with cakes today, and she has to put in each box several (at least one) cakes the oven produced one right after another (in other words, she has to put in a box a continuous segment of cakes).
Slastyona wants to maximize the total value of all boxes with cakes. Help her determine this maximum possible total value.
Input
The first line contains two integers n and k (1 ≤ n ≤ 35000, 1 ≤ k ≤ min(n, 50)) – the number of cakes and the number of boxes, respectively.
The second line contains n integers a1, a2, …, an (1 ≤ ai ≤ n) – the types of cakes in the order the oven bakes them.
Output
Print the only integer – the maximum total value of all boxes with cakes.
Examples
inputCopy
4 1
1 2 2 1
outputCopy
2
inputCopy
7 2
1 3 3 1 4 4 4
outputCopy
5
inputCopy
8 3
7 7 8 7 7 8 1 7
outputCopy
6
Note
In the first example Slastyona has only one box. She has to put all cakes in it, so that there are two types of cakes in the box, so the value is equal to 2.
In the second example it is profitable to put the first two cakes in the first box, and all the rest in the second. There are two distinct types in the first box, and three in the second box then, so the total value is 5.
题目大意:
有一个长度为n的序列,将它分为k段,定义每段的值为该段中不重复元素的个数,求k段值之和的最大值
如:n=4,k=2
1 2 2 1
可分为1,2和2,1两段,值之和为4
分析
1.DP方程的推导
设dp[i][j]dp[i][j]表示前j个数分为i段的最大值
状态转移方程为:
dp[i][j]=max(dp[i][j],dp[i−1][x−1]+num[x][j])dp[i][j]=max(dp[i][j],dp[i−1][x−1]+num[x][j])
其中x∈[i,j]x∈[i,j], num[x][j]num[x][j]表示区间[x,j][x,j]中不同的数的个数
dp本身的时间复杂度为O(n2k)O(n2k),遍历数组求num的时间复杂度为O(n)O(n),总时间复杂度为O(n3k)O(n3k)
2.求不同数个数的优化
设上一个等于a[i]的数的位置为last[i](如果是第一次,则last[i]=0)last[i](如果是第一次,则last[i]=0)
那么
处理到a[j]时,设x从last[j]+1到j,从last[i]+1到j的num[x][j]值均要加1
这是因为从last[i]+1到j的数一定没有a[j],加入a[j]后,不同的数个数一定多了1个
如:
a: 1 2 2 1
last: 0 0 2 1
如果不明白的话,可以在纸上模拟一下过程,但要确保理解上面这句话
3.线段树的优化
我们将状态转移方程中的dp[i−1][x−1]+num[x][j]dp[i−1][x−1]+num[x][j]看作一个整体,发现可以用线段树维护[i,j]最大值,这样只要一次查询就能求出max
另外,从last[i]+1到i的num[x][i]值均要加1这一操作,可以用区间修改来实现
所以我们只要枚举i从1到k,
每次对上一次的dp值(dp[i-1])建树
再枚举j从i到n,用区间修改,然后查询就能求出dp[i][j]的值
建树复杂度O(nlog2n)O(nlog2n)
n次修改和查询时间复杂度为O(nlog2n)O(nlog2n)
总时间复杂度为O(nklog2n)O(nklog2n)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 35005
#define maxk 55
using namespace std;
int n,k;
int a[maxn];
int hash[maxn],last[maxn];
int dp[maxk][maxn]; //dp[i][j]表示前j个数分成i段的方法数
struct node{
int l;
int r;
int v;
int mark;
}tree[maxn<<2];
void push_up(int pos){
tree[pos].v=max(tree[pos<<1].v,tree[pos<<1|1].v);
}
void build(int i,int l,int r,int pos){
tree[pos].l=l;
tree[pos].r=r;
tree[pos].mark=0;
if(l==r){
tree[pos].v=dp[i][l-1];
return;
}
int mid=(l+r)>>1;
build(i,l,mid,pos<<1);
build(i,mid+1,r,pos<<1|1);
push_up(pos);
}
void push_down(int pos){
if(tree[pos].mark!=0){
tree[pos<<1].mark+=tree[pos].mark;
tree[pos<<1|1].mark+=tree[pos].mark;
tree[pos<<1].v+=tree[pos].mark;
tree[pos<<1|1].v+=tree[pos].mark;
tree[pos].mark=0;
}
}
void update(int L,int R,int v,int pos){
if(L<=tree[pos].l&&R>=tree[pos].r){
tree[pos].v+=v;
tree[pos].mark+=v;
return;
}
push_down(pos);
int mid=(tree[pos].l+tree[pos].r)>>1;
if(L<=mid)update(L,R,v,pos<<1);
if(R>mid)update(L,R,v,pos<<1|1);
push_up(pos);
}
int query(int L,int R,int pos){
if(L<=tree[pos].l&&R>=tree[pos].r){
return tree[pos].v;
}
push_down(pos);
int mid=(tree[pos].l+tree[pos].r)>>1;
int ans=0;
if(L<=mid)ans=max(ans,query(L,R,pos<<1));
if(R>mid) ans=max(ans,query(L,R,pos<<1|1));
return ans;
}
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
last[i]=hash[a[i]];//last[i]->a[i]上一次出现的位置
hash[a[i]]=i;//hash[x]-> x上一次出现的位置
}
//dp[i][j]=max(dp[i-1][x-1]+num[x][j],dp[i][j]) (i<=x<=j,num[x][j]->[x,j]不同数的个数
//线段树维护 dp[i-1][x-1]+num[x][j],变成区间查询最大值问题
//加入a[j],从last[j]+1到j的所有num值都要+1 ,用线段树区间更新
for(int i=1;i<=k;i++){
build(i-1,1,n,1);
for(int j=i;j<=n;j++){
update(last[j]+1,j,1,1);
dp[i][j]=query(i,j,1);
}
}
printf("%d\n",dp[k][n]);
}
Codeforces 833B 题解(DP+线段树)的更多相关文章
- Codeforces.833B.The Bakery(线段树 DP)
题目链接 \(Description\) 有n个数,将其分为k段,每段的值为这一段的总共数字种类,问最大总值是多少 \(Solution\) DP,用\(f[i][j]\)表示当前在i 分成了j份(第 ...
- Codeforces 1093G题解(线段树维护k维空间最大曼哈顿距离)
题意是,给出n个k维空间下的点,然后q次操作,每次操作要么修改其中一个点的坐标,要么查询下标为[l,r]区间中所有点中两点的最大曼哈顿距离. 思路:参考blog:https://blog.csdn.n ...
- Codeforces Round #620 F2. Animal Observation (hard version) (dp + 线段树)
Codeforces Round #620 F2. Animal Observation (hard version) (dp + 线段树) 题目链接 题意 给定一个nm的矩阵,每行取2k的矩阵,求总 ...
- [Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路)
[Codeforces 1197E]Culture Code(线段树优化建图+DAG上最短路) 题面 有n个空心物品,每个物品有外部体积\(out_i\)和内部体积\(in_i\),如果\(in_i& ...
- Codeforces Gym 100231B Intervals 线段树+二分+贪心
Intervals 题目连接: http://codeforces.com/gym/100231/attachments Description 给你n个区间,告诉你每个区间内都有ci个数 然后你需要 ...
- cf834D(dp+线段树区间最值,区间更新)
题目链接: http://codeforces.com/contest/834/problem/D 题意: 每个数字代表一种颜色, 一个区间的美丽度为其中颜色的种数, 给出一个有 n 个元素的数组, ...
- Buses and People CodeForces 160E 三维偏序+线段树
Buses and People CodeForces 160E 三维偏序+线段树 题意 给定 N 个三元组 (a,b,c),现有 M 个询问,每个询问给定一个三元组 (a',b',c'),求满足 a ...
- CodeForces 877E DFS序+线段树
CodeForces 877E DFS序+线段树 题意 就是树上有n个点,然后每个点都有一盏灯,给出初始的状态,1表示亮,0表示不亮,然后有两种操作,第一种是get x,表示你需要输出x的子树和x本身 ...
- ZOJ 3349 Special Subsequence 简单DP + 线段树
同 HDU 2836 只不过改成了求最长子串. DP+线段树单点修改+区间查最值. #include <cstdio> #include <cstring> #include ...
随机推荐
- windows 安装apache2.4
https://segmentfault.com/a/1190000008767607 一.下载 1.下载地址: http://httpd.apache.org/download.cgi 2.找到Fi ...
- MySQL数据库2表的增删改查
目录 一.数据表(文件): 1.1增 1.2查看表内数据 1.3改 1.4删除列表 1.5查看库内列表及表结构 1.6复制表结构 二.列类型:(*********) 2.1数字 2.2字符串 2.3时 ...
- python补充之进制转换、exec、eval、compile
目录 eval.exec和compile 1.eval函数 2.exec函数 eval()函数和exec()函数的区别 python中的进制转换 eval.exec和compile 1.eval函数 ...
- [洛谷P1864] NOI2009 二叉查找树
问题描述 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结点的权值都比它的 ...
- sql server 修改表字段
1.添加表说明 EXECUTE sp_addextendedproperty N'MS_Description','表说明',N'user',N'dbo',N'table',N'表名',NULL,NU ...
- Bugku 杂项 隐写
隐写 下载后打开压缩包发现是一张图片 用winhex打开 图中红色框内是PNG的PE头 在IHDR后面的八个字节(黄色框部分)为该图片的长度.宽度信息 将黄色框内最后一个字节由A4改为F4后另存为图片 ...
- Python_019(六星级别之反射方法)
1.反射 1)神赐给你的内置函数 : a: getattr(命名空间,'函数名') == 命名空间.属性名; 这里的命名空间指的是对象或者类; b: getattr四个应用场景: 1)类名.名字 &l ...
- 使用tushare获取股票历史数据和实时分笔数据
使用tushare获取股票历史数据和实时分笔数据 财经数据接口包tushare的使用(一) Tushare是一款开源免费的金融数据接口包,可以用于获取股票的历史数据.年度季度报表数据.实时分笔数据 ...
- React-Native 之 GD (五)属性声明和属性确认 及 占位图
1.在 React-Native 创建的自定义组件是可以复用的,而开发过程中一个组件可能会由多个人同时开发或者多个人使用一个组件,为了让开发人员之间减少沟通成本,我们会对某些必要的属性进行属性声明,让 ...
- springMVC解决跨域
原文:https://www.cnblogs.com/shihaiming/p/9544060.html 介绍: 跨站 HTTP 请求(Cross-site HTTP request)是指发起请求 ...