Codeforces 487B. Strip(求区间最值+线段树上的dp)
来源:互联网 发布:做网页用什么软件 编辑:程序博客网 时间:2024/05/16 11:24
题意:将一个长度为n的数列划分成m个部分,要求每个部分含有的个数>=L,且每个部分最大值-最小值<=S,
求满足上述两个条件情况下m的最小值。即划分区间个数最小
题目地址:http://codeforces.com/contest/487/problem/B
为了形象地说明,举题目第一个栗子如下:
我们得到上列数列,A1-A7,长度为7
A1=1 , A2=3 , A3=1 , A4=2 , A5=4 , A6=1 , A7=2;
根据题目,每一个区间必须满足两个条件:条件一, 区间内元素个数>=L ; 条件二, 区间最大值-最小值<=S
对于每个位置 i ,我们找到以 i 为左端点,往右拓展的区间,只考虑满足条件二时候,最远可到的位置是哪里。
A1=1,以1为左端点,我们可以得到7个区间
即 [A1,A1] : 1
[A1,A2] : 1 3
[A1,A3] : 1 3 1
[A1,A4] : 1 3 1 2
[A1,A5] : 1 3 1 2 4
[A1,A6] : 1 3 1 2 4 1
[A1,A7] : 1 3 1 2 4 1 2
我们容易得出每个区间最大值-最小值 的值,定义maxsubmin为每个区间最大值-最小值的值
则:
maxsubmin[A1,A1]=0
maxsubmin[A1,A2]=2
maxsubmin[A1,A3]=2
maxsubmin[A1,A4]=2
maxsubmin[A1,A5]=3
maxsubmin[A1,A6]=3
maxsubmin[A1,A7]=3
栗子一里S=2,意思是任意一个选取的区间maxsubmin值必须<=2
由此可知,从A1开始的区间中只有[A1,A1],[A1,A2],[A1,A3],[A1,A4] 满足条件二
所以,从A1开始,往右拓展的区间,只考虑满足条件二的时候,最远可到达位置为 4
不妨定义 maxto[i] 表示从 i 开始,往右拓展的区间,只考虑条件二的时候,最远可到达的位置为 maxto[i]
对于原数列,我们很容易得出每个位置 i 的值 maxto[i],如下图所示
我们得到了maxto[i],表示的是从位置 i 开始,向右拓展,只考虑满足条件二时,最远可到达的位置为 maxto[i]
容易发现,假设此时要从位置 i 开始往右划分一段区间,根据条件一,区间元素个数必须>=L,也就是,从位置 i 开始,至少需要划到 i + L -1的位置
而当从位置 i 开始,即使划到位置n,区间元素个数也小于L的话,就不能从 i 开始往右划分。因为此时不满足条件一
我们很容易想到特判的条件,当n<L的时候,即数列个数小于L,无法满足条件一
同样地,从位置 i 开始,我已经知道只考虑条件二时最远可达位置 maxto[i],此时如果maxto[i]-i+1,也就是区间元素个数,小于L的话,就不能从 i 往右进行划分;
在以上的基础上,我们就可以动态规划地解决题目的问题
定义dp数组 dp[i]
dp[i] 表示 从1到 i-1这段区间上最小划分的区间个数
同样,以栗子一举例
我们已经知道,从A1开始的区间中只有[A1,A1],[A1,A2],[A1,A3],[A1,A4] 满足条件二
但只有[A1,A2],[A1,A3],[A1,A4] 既满足条件二,又满足条件一
则,dp[3]=1,dp[4]=1,dp[5]=1
拿dp[3]举例,表示从1到2这段去区间上,划分的区间个数为1个,即[A1,A2]这一个区间
我们继续将这个步骤进行下去
如果我们从3处开始往右划分区间,根据之前得出的maxto[3]和题目给的L可以知道,只有[A3,A3],[A3.A4]满足题意
所以dp[4]=min(dp[4],dp[3]+1),dp[5]=min(dp[5],dp[3]+1);
dp[3]+1=2,表示从1到2可以划分最小区间个数为1,从3到3 或 3到4,又可以进行一次划分,所以,从1到3 或 从1到4 可以进行两次划分
但是,我们从1到3 和 1到4 可以进行一次划分,所以根据题意,我们取更小的那次
dp[4]=min(dp[4],dp[3]+1)=1,dp[5]=min(dp[5],dp[3]+1)=1;
这个样子,动态规划的思想就很显然了
对于每个位置 i ,我们从之前的位置维护得到,从1到 i-1 这段区间最少可以划分成几个区间
那么,此时,我们再从 i 位置开始往右划分,再维护 i 位置右边的dp值。这样子answer=dp[n+1],表示1到n区间最少可以划分成几个区间,就是我们要的答案
对于栗子一,我们可以得到一下dp数组:
其中INT表示正无穷,即2之前的区间[1,1]不能划分成区间(违背了条件一)
可以发现,answer=dp[8]=3,就是栗子一的答案
核心转移方程:dp[j]=min(dp[j],dp[i]+1);
初始化:dp[1]=0,dp[其他]=无穷大(实际上,用1e9表示就够了)
而dp伪代码如下:
for(int i=1;i<=n;i++){ dp[i]=无穷大;}dp[1]=0;
for(int i=1;i<=n;i++){ if(从i开始到n这段区间元素个数<L) continue; if(dp[i]==无穷大) continue; int to=i+L-1; if(maxto[i]<to) continue; for(int j=to+1;j<=maxto[i]+1;j++){ dp[j]=min(dp[j],dp[i]+1); }}
但是!!
这么做是过不了这道题的=。=
用动态规划做没有问题,dp思想也是正确的,而问题出在复杂度上
可以发现上面伪代码的复杂度是:O(n^2),n=10^5,很显然不可能1s解决问题
所以,这里就涉及到了标题所提到的,在线段树上进行dp
我们会发现上串为代码有这样一个for循环:
for(int j=to+1;j<=maxto[i]+1;j++){ dp[j]=min(dp[j],dp[i]+1); }
这个for循环,实际上就是,将[to+1,maxto[i]+1]这段区间上dp值dp[j]与dp[i]+1取最小值。这是可以通过线段树懒操作来优化的
具体如何通过线段树来优化,我不愿意详述,相信熟悉线段树(需要用到懒操作)的朋友可以很容易写出来
如果不熟悉线段树的朋友,可以看我下面代码。不过建议多写线段树的懒操作,这样子才能有本质的提高。
线段树dp核心代码如下:
//线段树dp:#define MAXN 100100#define INT (0x3f3f3f3f)*2struct Segment_Tree{ int left,right; int dp,lazy;}tree[4*MAXN];void plant_tree(int id,int l,int r){ tree[id].left=l,tree[id].right=r; tree[id].lazy=INT; if(l==r){ if(l==1) tree[id].dp=0; else tree[id].dp=INT; return; } int mid=(l+r)>>1; plant_tree(id<<1,l,mid); plant_tree((id<<1)+1,mid+1,r); tree[id].dp=min(tree[id<<1].dp,tree[(id<<1)+1].dp);}void push_down(int id){//懒操作pushdown if(tree[id].lazy==INT) return; tree[id].dp=min(tree[id].dp,tree[id].lazy); if(tree[id].left==tree[id].right){ tree[id].lazy=INT; return; } tree[id<<1].lazy=min(tree[id<<1].lazy,tree[id].lazy); tree[(id<<1)+1].lazy=min(tree[(id<<1)+1].lazy,tree[id].lazy); tree[id].lazy=INT;}void update_leaf(int id,int l,int r,int val){ if(tree[id].left==l && tree[id].right==r){ tree[id].lazy=min(tree[id].lazy,val); push_down(id); return; } push_down(id); int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) update_leaf(id<<1,l,r,val); else if(mid<l) update_leaf((id<<1)+1,l,r,val); else{ update_leaf(id<<1,l,mid,val); update_leaf((id<<1)+1,mid+1,r,val); }}int query(int id,int l,int r){ if(tree[id].left==l && tree[id].right==r){ push_down(id); return tree[id].dp; } push_down(id); int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) return query(id<<1,l,r); else if(mid<l) return query((id<<1)+1,l,r); else return min(query(id<<1,l,mid),query((id<<1)+1,mid+1,r));}
此题需要用到两棵线段树,一棵来查询a[i]任意区间上的最大最小值,另外一棵线段树就是用来动态规划解决问题
总代码如下:
//Hello. I'm Peter.#include<cstdio>#include<iostream>#include<sstream>#include<cstring>#include<string>#include<cmath>#include<cstdlib>#include<algorithm>#include<functional>#include<cctype>#include<ctime>#include<stack>#include<queue>#include<vector>#include<set>#include<map>using namespace std;typedef long long ll;typedef long double ld;#define peter cout<<"i am peter"<<endl#define input freopen("data.txt","r",stdin)#define randin srand((unsigned int)time(NULL))#define INT (0x3f3f3f3f)*2#define LL (0x3f3f3f3f3f3f3f3f)*2#define gsize(a) (int)a.size()#define len(a) (int)strlen(a)#define slen(s) (int)s.length()#define pb(a) push_back(a)#define clr(a) memset(a,0,sizeof(a))#define clr_minus1(a) memset(a,-1,sizeof(a))#define clr_INT(a) memset(a,INT,sizeof(a))#define clr_true(a) memset(a,true,sizeof(a))#define clr_false(a) memset(a,false,sizeof(a))#define clr_queue(q) while(!q.empty()) q.pop()#define clr_stack(s) while(!s.empty()) s.pop()#define rep(i, a, b) for (int i = a; i < b; i++)#define dep(i, a, b) for (int i = a; i > b; i--)#define repin(i, a, b) for (int i = a; i <= b; i++)#define depin(i, a, b) for (int i = a; i >= b; i--)#define pi 3.1415926535898#define eps 1e-6#define MOD 1000000007#define MAXN 100100#define N#define M//线段树求最值struct Segment_TreeforRMQ{ int left,right; int min,max;}treeRMQ[4*MAXN];void plant_treeforRMQ(int id,int l,int r,Segment_TreeforRMQ* tree,int *a){ tree[id].left=l,tree[id].right=r; if(l==r){ tree[id].min=tree[id].max=a[l]; return; } int mid=(l+r)>>1; plant_treeforRMQ(id<<1,l,mid,tree,a); plant_treeforRMQ((id<<1)+1,mid+1,r,tree,a); tree[id].min=min(tree[id<<1].min,tree[(id<<1)+1].min); tree[id].max=max(tree[id<<1].max,tree[(id<<1)+1].max);}int query_min(int id,int l,int r,Segment_TreeforRMQ* tree){ if(tree[id].left==l && tree[id].right==r){ return tree[id].min; } int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) return query_min(id<<1,l,r,tree); else if(mid<l) return query_min((id<<1)+1,l,r,tree); else return min(query_min(id<<1,l,mid,tree),query_min((id<<1)+1,mid+1,r,tree));}int query_max(int id,int l,int r,Segment_TreeforRMQ* tree){ if(tree[id].left==l && tree[id].right==r){ return tree[id].max; } int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) return query_max(id<<1,l,r,tree); else if(mid<l) return query_max((id<<1)+1,l,r,tree); else return max(query_max(id<<1,l,mid,tree),query_max((id<<1)+1,mid+1,r,tree));}//查询最大值-最小值int maxsubmin(int from,int to){ return query_max(1,from,to,treeRMQ)-query_min(1,from,to,treeRMQ);}//线段树dp:struct Segment_Tree{ int left,right; int dp,lazy;}tree[4*MAXN];void plant_tree(int id,int l,int r){ tree[id].left=l,tree[id].right=r; tree[id].lazy=INT; if(l==r){ if(l==1) tree[id].dp=0; else tree[id].dp=INT; return; } int mid=(l+r)>>1; plant_tree(id<<1,l,mid); plant_tree((id<<1)+1,mid+1,r); tree[id].dp=min(tree[id<<1].dp,tree[(id<<1)+1].dp);}void push_down(int id){//懒操作pushdown if(tree[id].lazy==INT) return; tree[id].dp=min(tree[id].dp,tree[id].lazy); if(tree[id].left==tree[id].right){ tree[id].lazy=INT; return; } tree[id<<1].lazy=min(tree[id<<1].lazy,tree[id].lazy); tree[(id<<1)+1].lazy=min(tree[(id<<1)+1].lazy,tree[id].lazy); tree[id].lazy=INT;}void update_leaf(int id,int l,int r,int val){ if(tree[id].left==l && tree[id].right==r){ tree[id].lazy=min(tree[id].lazy,val); push_down(id); return; } push_down(id); int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) update_leaf(id<<1,l,r,val); else if(mid<l) update_leaf((id<<1)+1,l,r,val); else{ update_leaf(id<<1,l,mid,val); update_leaf((id<<1)+1,mid+1,r,val); }}int query(int id,int l,int r){ if(tree[id].left==l && tree[id].right==r){ push_down(id); return tree[id].dp; } push_down(id); int mid=(tree[id].left+tree[id].right)>>1; if(r<=mid) return query(id<<1,l,r); else if(mid<l) return query((id<<1)+1,l,r); else return min(query(id<<1,l,mid),query((id<<1)+1,mid+1,r));}void printno(){ printf("%d\n",-1); exit(0);}int n,s,l;int a[MAXN],maxto[MAXN];int main(){ cin>>n>>s>>l; repin(i,1,n){ scanf("%d",a+i); } if(n<l) printno();//当n<l的时候,不成立 plant_treeforRMQ(1,1,n,treeRMQ,a);//建立树,为了求a数组的最值 int to=1; repin(i,1,n){//求出只满足maxi-mini<=s的条件下,每个点i最远可往右到哪个点 to=max(to,i); while(1){ if(to==n){ maxto[i]=to; break; } int t=maxsubmin(i,to+1); if(t<=s) to+=1; else{ maxto[i]=to; break; } } } plant_tree(1,1,n+1);//线段树dp,建树 repin(i,1,n){//dp,用线段树优化 if(n-i+1<l) continue; int now=query(1,i,i); if(now==INT) continue; int to=i+l-1; if(maxto[i]<to) continue; update_leaf(1,to+1,maxto[i]+1,now+1); } int ans=query(1,n+1,n+1); if(ans==INT) ans=-1; printf("%d\n",ans);}
- Codeforces 487B. Strip(求区间最值+线段树上的dp)
- Codeforces 487B. Strip DP+线段树+二分
- codeforces 487B Strip dp
- 【DP】 codeforces 487B Strip
- codeforces 487B B. Strip(rmq+线段树+二分)
- CodeForces 487B Strip
- codeforces 487 B. Strip
- CodeForces 487B Strip
- Codeforces 533B 树上的dp(求最大偶数个节点的权重和)
- Codeforces 487b Strip, dp + RMQ(经典)
- 【Monotonic-queue】【dp】【Segment-tree】【STL】Codeforces 487B - Strip
- CodeForces 487 B.Strip(dp+尺取+set)
- POJ2823 - 线段树求区间的最值..
- 线段树三:求任意区间的最值
- Codeforces 487B Strip(RMQ)
- 线段树求区间最值
- 线段树求区间最值
- POJ2823(线段树求区间最值)
- 【数据结构+线段树】连续型/离散型线段树
- 的撒打算;打卡上;打卡上;打开;阿什利看到;拉SD卡
- varnish
- 算法分类
- Storm杂谈之Topology的启动过程(一)
- Codeforces 487B. Strip(求区间最值+线段树上的dp)
- 左键获取当前鼠标选中的文件的路径
- Android中error inflating class fragment
- 给我任何污染环境危机而
- Browse SBI branches and their IFSC Codes of Srikakulam
- cocos2dx FadeIn到底怎么用?
- android adt SDK无法下载的解决办法
- String,Timestamp,Date(java.util.date,java,sql.date)相互转化
- android developer