单调性+单调队列+DP优化=(大坑)
单调性
单调性, 在oi中许多算法中,都有用到,最经典的莫属二分 。当然对于函数驻点只有一个的类二次和二次函数,我们还有三分。但是我们今天不讲分治。
单调递增,单调递减不知道是啥?其实我觉得,没必要知道这玩意。。当时学的时候,也不没学到函数,(虽然听着一脸懵)。。
所以之所以说单调性。。。是为了。。。为了。。。。(好玩)。。。
单调队列。。(单调的一个函数(还要自己维护))
单调队列,单调队列,结构如其名,数组中的数要维护成单调形式。。
具体的做法。。我们先贴代码。。
#define REP(i,a,b) for(int i(a);i<=(b);++i)
REP(i,1,n){
while(c[head]<=i-k&&head<=tail) head++;
while(a[c[tail]]>a[i]&&tail>=head) tail--;
c[++tail]=i;if (i>=k) cout<<a[c[head]]<<" ";}
下面再慢慢解析,,,head是头指针,tail是尾指针。。这里的代码是从sliding windows上才下来的。因为和模板没区别链接是---------滑动窗口我们想 单调队列。要维护单调,如果我们直接在数组中存数,后期在删数时就不好了。。所以第一步要知道的是一般存的都是坐标(位置);(删数不知道?下面讲)
既然要维护单调,我们就想,有多少条件在限制呢。。。
想滑动窗口这题
首先,他有一个窗口在数组上滑动,,滑呀滑,可以看到,后面一直有数在进来,前面有数一直在出去。。 如果我们要维护这样一个单调的队列就一定也要有加入和弹出操作
while(c[head]<=i-k&&head<=tail) head++;
可以看到有两个限制语句,而语句的结果就是head++;
想一想,如果head++了;那之前的head里的坐标就pop()出去了。。(手动滑稽)
可以联想一下队列。。。而至于限定语句
我们来慢慢讲
c数组是单调队列,里面存的都是下标,,那么这一句表达的是夏目意思捏
如果c中存的下标已经不满足条件了,我们就应该把它pop()出去
i-k就是现在我们的在数组中滑动的“窗口”的第一个最小开头的坐标
这样就可以保证队列中的数一定在我们要查询的范围内,,
啊,你说如果后面有下标不符合条件的怎么办???
额,我没考虑,
因为不需要考虑。。。。
因为我们的放入,已经把这个问题搞掉了。。。
while(a[c[tail]]>a[i]&&tail>=head) tail--;c[++tail]=i;
可以看到这一句c[++tail]=i ; 这一句保证了前面的下标一定比后面的要小。。
因为REP保证了i是递增的,tail是从尾部加入数,可以理解为筛选i,从单调递增的函数上从前往后取数,一定是单调的(没学过的话(自个yy));
前面还有限制语句,这里最难(个人觉得)当时上的时候(一脸懵逼)
单调(单调)!!!!记住是单调的!
所以我们要维护这样一个单调队列,,如果前面有比他小的(大的)就把他pop()出去
这样才能形成部分单调,,这是从数学的角度
实用的角度
比如我们知道,,单调队列一般都是用于求最值smax,smin啥的。所以我们维护队列的目的最终是为了求smin(在一定范围内)
对于像滑动窗口这种题目,如果真的爆搜,我们会发现。(特不现实)TTTTT飞了
然后我们坐下来分析,,诶后面的好像可以从前面的得出来!,我们可以比较一下最大值(最小值)比我要加的数哪个大好像就可以了!(XXXXXXXXX)
错的一塌糊涂,,,里面有太多要维护的了(因为会删去有用的节点,,(可以自己想想))。。所以一切都集中在这(有用)。。如果我们保证队列中都时有用的呢!!!!
这时候回到代码
while(a[c[tail]]>a[i]&&tail>=head) tail--;c[++tail]=i;
如果现在最后的单调队列最后面下标代表的数比我放的数(大)小,按需求。。我们就把他踢出去,,,
比如举个例子。。我们要求最大值,,我们放进去一个数;然后发现!前面竟然有个比自己大的(更早放进去),但是却比自己弱(比自己小)的吃闲饭的!为甚么呢(你想想后面的数一定比前面的数后被++head)也就是说我放的数存在的时候,前面的数不一定存在。
但前面的数存在的话,我们放进去的数一定一定一定存在(在放这个数字后)!
那么他比我弱,他存在,我一定存在,我要最优解,最优解不可能是他~~!!!
那么还要他干嘛!!!直接–tail,带走他,。。。然后自己坐上他的位置。。
c[++tail]=i;
一波操作后就是这样一个过程,完成了维护,,,
贴代码
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i(a);i<=(b);++i)
using namespace std;
int n,k,a[1000110],c[1000110];
int main(){
std::ios::sync_with_stdio(false);
cin>>n>>k;
REP(i,1,n) cin>>a[i];a
int head=1,tail=0;
REP(i,1,n){
while(c[head]<=i-k&&head<=tail) head++;
while(a[c[tail]]>a[i]&&tail>=head) tail--;
c[++tail]=i;if (i>=k) cout<<a[c[head]]<<" ";}
memset(c,0,sizeof(c));head=1; tail=0; cout<<endl;
REP(i,1,n){
while(c[head]<=i-k&&head<=tail) head++;
while(a[c[tail]]<a[i]&&tail>=head) tail--;
c[++tail]=i;if (i>=k) cout<<a[c[head]]<<" "; }
}
题目意思是自己看
下面是拓展
DP优化!!单调的上司~~
单调队列裸题,在洛谷中只有绿题难度,,,可是他却让无数dalao __orz__这是为啥
因为和DP这个坑扯上关系了!!!
下面开始讲 琪露诺
首先看三秒题面。。DP。。土的不能再土的线性DP
然后5分钟代码。。两秒钟T飞。。。。
诶诶诶,咋回事。。。。
我们来看看DP 转移。。
DP[i]=max(DP[i-k])为能跳到这点的格子。。。
一个一个枚举。。。好暴力啊
再看看,再看看滑动窗口,你是否看到了那一个滑动的“窗口”!!
DP[i+1]与DP[i]的枚举范围只差两个,一个头,一个尾,头出去,尾进来。。然后又是一进一出,,又是。。。这不是滑动窗口吗!!!!!
下面就要你自己意会了,我已经尽力了。。这东西很玄学。。
贴代码
早期写的。。(有点丑)
#include<bits/stdc++.h>
using namespace std;
int dp[400010];
int n,r,l;
int head=1,tail=1;
int b[400010];
int a[400010];
int main(){
memset(dp,0,sizeof(dp));
std::ios::sync_with_stdio(false);
cin>>n>>r>>l;
for (int i=0; i<=n;++i){
int x; cin>>x;
a[i]=x;
} b[1]=0;
for (int i=r;i<=n+l;++i){
while (head<=tail&&dp[b[tail]]<=dp[i-r]) --tail;
b[++tail]=i-r;
while (b[head]<i-l) ++head;
dp[i]=dp[b[head]]+a[i];
}
int maxn=-0x7f7f7f7f;
for (int i=n+1;i<=n+l;++i)
maxn=max(dp[i],maxn);
cout<<maxn<<endl;
}
差不多了
来一句
@)!()NOIP RP==111111111111111
回头再来看都已经一年了
补几道题目
首先列出dp方程式
dp的思路是,当我们已经选到了第i头奶牛可以得到的最大效率
而在这前i头奶牛中我们规定,一定是i-k—i中的一头奶牛可以休息,当然当i-k<0是就可以直接使DP[i]=sum[i]
所以
DP[i]=max(dp[j-1]-sum[j]+sum[i],dp[i])
其中的 (i-k<=j<=i)
然后我们发现方程式中的DP[j-1]和sum[j]是都是已知的,而且都是有顺序的往后移动,所以可以用单调队列来进行维护最大值
题解如下
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i(a);i<=(b);++i)
#define MAXN 4000010
using namespace std;
void read(long long &x){
x=0; char c=getchar(); int f=1;
for(;!isdigit(c);c=getchar()) if (c=='-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f;
}
long long n,k,s[MAXN],sum[MAXN],dp[MAXN],c[MAXN],head,tail;
long long num(int pla){
return dp[pla-1]-sum[pla];
}
int main(){
memset(dp,0,sizeof(dp));
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
read(n),read(k);
REP(i,1,n) read(s[i]),sum[i]=sum[i-1]+s[i];
// REP(i,1,n) cout<<sum[i]<<" "; cout<<endl;
head=1; tail=1; c[tail]=1;
REP(i,0,k) {
dp[i-1]=sum[i-1];
while (num(c[tail])<=num(i)&&tail>=head) tail--; ++tail;
// cout<<num(i)<<endl;
c[tail]=i;
}
// cout<<c[head]<<endl;
REP(i,k,n+1) {
while (c[head]<i-k) head++;
// int pla1;
if (num(c[head])+sum[i]>dp[i]) {
dp[i]=num(c[head])+sum[i];
// pla1=c[head];
}
// cout<<c[head]<<endl;
while (num(c[tail])<=num(i)&&tail>=head) tail--; ++tail;c[tail]=i;
}
// REP(i,n-100,n) cout<<dp[i]<<" "; cout<<endl;
cout<<dp[n]<<endl;
}
这题很水, 二分+单调队列,不解释
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i(a);i<=(b);++i)
#define MAXN 100010
using namespace std;
void read(long long &x){
x=0; char c=getchar(); int f=1;
for(;!isdigit(c);c=getchar()) if (c=='-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f;
}
struct node{
long long x,y;
}point[MAXN];
bool cmp(node a,node b){
return a.x<b.x;
}
long long C[MAXN],n,d,l,r,c[MAXN];
long long check(long long lim){
// cout<<lim<<endl;
long long head=1,tail=0;
long long Head=1,Tail=0;
REP(i,1,n) {
while (point[c[tail]].y<=point[i].y&&head<=tail) --tail; c[++tail]=i;
while (point[c[head]].x<point[i].x-lim) head++;
while (point[C[Tail]].y>=point[i].y&&Head<=Tail) --Tail; C[++Tail]=i;
while (point[C[Head]].x<point[i].x-lim) Head++;
// cout<<point[c[head]].y-point[C[Head]].y<<endl;
if (point[c[head]].y-point[C[Head]].y>=d) return 1;
}
return 0;
}
int main(){
// freopen("测试数据.txt","r",stdin);
// freopen("测试结果.txt","w",stdout);
read(n),read(d);
REP(i,1,n) read(point[i].x),read(point[i].y);
sort(point+1,point+1+n,cmp);
l=1; r=point[n].x+10;
while (l<r) {
int mid=(l+r)>>1;
if (check(mid)) r=mid; else l=mid+1;
}
if (l>point[n].x) cout<<-1<<endl;
else cout<<l<<endl;
return 0;
}
翻译一下
2018NOIP RP++!!!
THE END