标签:style blog http io ar color os sp for
题意:将一个长度为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)*2
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));
}
此题需要用到两棵线段树,一棵来查询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)
标签:style blog http io ar color os sp for
原文地址:http://blog.csdn.net/peteryuanpan/article/details/41402053