单调队列DP
HDU5945
题意
给定整数,存在如下两种操作。
1.
2.如果
求让变为
的最小操作次数。
题解
状态定义:表示将
变成
的最小次数
目标:
边界:,其他为
转移方程:
上面的方程时间复杂度为,于是考虑优化。
我们发现,对于外层来说,内层的j的范围为
。
因为这个范围的左右端点都会随着i的增大而增大,而转移时需要的是这个范围内的最小值,所以可以考虑用一个单调递增的单调队列来维护上的最小
值下标。
PS:
使用单调队列优化计算第i个状态时的步骤。
1 判断队首是否满足条件,不满足则不断弹出队首。
2 利用队首来更新状态i的答案。(如果是单调栈,则此时利用队尾来更新状态i的答案)
3 判断如果加入后,队尾队首是否满足条件,不满足则不断弹出队尾。
4 从队尾插入。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=1e6+5;
const int INF=0x3f3f3f3f;
int T;
int X,k,t;
int f[maxn];
int q[maxn],head,tail;
void init(){
memset(f,INF,sizeof(f));
head=1,tail=0;
}
void dp(){
f[1]=0;
q[++tail]=1;
for(int i=2;i<=X;i++){
while(tail-head+1>t) head++;
if(i%k==0) f[i]=min(f[i],f[i/k]+1);
f[i]=min(f[i],f[q[head]]+1);
while(head<=tail&&f[q[tail]]>f[i]) tail--;
q[++tail]=i;
}
cout<<f[X]<<endl;
}
int main() {
ios::sync_with_stdio(false);
//freopen("HDU5945.in","r",stdin);
cin>>T;
while(T--){
cin>>X>>k>>t;
init();
dp();
}
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
BZOJ1293
题意
个珠子,共有
种,给定每个珠子的坐标,求最短区间,使得该区间包含所有种类的珠子。
题解
这题放在单调队列优化DP一章里,我想了半天没有头绪。
结果最后发现可以直接贪心。
首先将珠子坐标升序排序后,用表示第
种珠子最后一次出现的位置。
则从左往右扫描时,一旦发现已经能包括所有珠子时,就用数组来更新答案即可。
唯一的坑点在于,最后答案可能很大,所以最大值不用能,最少为
。
时间复杂度。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=1000000+5;
const int maxk=60+5;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n,k;
struct node{
int type;
ll x;
// node(int _type,ll _x){
// type=_type,x=_x;
// }
bool operator<(const node& h)const{return x<h.x;}
}p[maxn];
ll last[maxk]; //last[i]表示第i种珠子最后一次出现的位置
ll ans=INF;
void solve(){
sort(p+1,p+n+1);
/*
for(int i=1;i<=n;i++){
D(p[i].type);D(p[i].x);E;
}
*/
for(int i=1;i<=n;i++){
last[p[i].type]=p[i].x;
int cnt=0;
ll minx=INF;
for(int j=1;j<=k;j++){
if(last[j]){
cnt++;
minx=min(minx,last[j]);
}
}
if(cnt==k){
// D(p[i].x);D(minx);E;
ans=min(ans,p[i].x-minx);
}
}
printf("%lld",ans);
}
int main() {
// ios::sync_with_stdio(false);
//freopen("BZOJ1293.in","r",stdin);
scanf("%d%d",&n,&k);
int tot=0;
for(int i=1;i<=k;i++){
int num;
scanf("%d",&num);
for(int j=1;j<=num;j++){
ll x;
scanf("%lld",&x);
p[++tot].type=i;
p[tot].x=x;
}
}
solve();
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
BZOJ1499
题意
行
列
的矩阵中,有些点不可通行。
有一台钢琴初始位于,每个时刻,它可以选择,是否根据矩阵的倾斜方向,向着上下左右四个方向移动。
已知全程的矩阵倾斜状态(倾斜状态用个区间表示),求钢琴移动距离的最大值。
题解
状态定义:表示到第
个时间段,钢琴位于
的最长移动距离
目标:
边界:,其他为
转移方程:为上一个合理的位置。
时间复杂度,空间复杂度
,都需要优化。
注意到仅仅依赖于
,因此可以压缩掉第一维,解决了空间限制。
注意到或
,因此可以每次只考虑当前区间所在的行/列,化二维为一维,用单调队列来维护区间最值。
题解链接 https://www.luogu.com.cn/problemnew/solution/P2254
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=200+5;
const int maxk=200+5;
const int INF=0x3f3f3f3f;
const int dx[]={0,-1,1,0,0};
const int dy[]={0,0,0,-1,1};
int n,m,k;
char a[maxn][maxn];
struct node{
int d,pos;
};
node q[maxn];
int head,tail;
int ans=-INF;
int d[maxn][maxn];
void init(){
head=1,tail=0;
}
bool check(int x,int y){
if(x<1||x>n||y<1||y>m) return false;
return true;
}
void dp(int x,int y,int len,int dir){
init();
int cnt=0;
while(1){
cnt++;
if(!check(x,y)) break;
if(a[x][y]=='x'){
init();
}
else{
while(head<=tail&&q[tail].d+cnt-q[tail].pos<d[x][y]) tail--; //队首存储最大值,因此为单调递减队列。
//这里之所以是q[tail].d+cnt-q[tail].pos,而不是q[tail].d。是因为需要纪录额外偏移量。
q[++tail] = (node){d[x][y], cnt}; //存储上一个时间段的状态d[t-1,,]
while(q[tail].pos-q[head].pos>len) head++;
d[x][y]=q[head].d+cnt-q[head].pos;//更新这一个时间段的状态d[t,,] dis(i*,j*,i,j)=cnt-q[head].pos
ans=max(ans,d[x][y]);
}
x+=dx[dir],y+=dy[dir];
}
}
int main() {
// ios::sync_with_stdio(false);
//freopen("BZOJ1499.in","r",stdin);
int sx,sy;
scanf("%d%d%d%d%d",&n,&m,&sx,&sy,&k);
memset(d,0xf3,sizeof(d));
d[sx][sy]=0;
for(int i=1;i<=n;i++){
getchar();
for(int j=1;j<=m;j++) scanf("%c",&a[i][j]);
}
/*
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) printf("%c",a[i][j]);
printf("\n");
}
*/
int s,t,dir;
while(k--){
scanf("%d%d%d",&s,&t,&dir);
// D(s);D(t);D(dir);E;
int len=t-s+1;
if(dir==1) for(int i=1;i<=m;i++) dp(n,i,len,dir);
if(dir==2) for(int i=1;i<=m;i++) dp(1,i,len,dir);
if(dir==3) for(int i=1;i<=n;i++) dp(i,m,len,dir);
if(dir==4) for(int i=1;i<=n;i++) dp(i,1,len,dir);
}
printf("%d",ans);
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
BZOJ3316
题意
个非负整数排成一圈,可以选出一个长度在
间,且为偶数的区间,得到区间平均值的贡献。求最大贡献。
题解
首先,通过倍长数组的方式,避开对环形的讨论。
其次,求最大平均值,可采用二分答案。
然后,为了高效的验证当前答案是否可行,可对序列的前缀和数组维护单调递增队列(这样队首元素最小)。
最后,因为区间长度必须为偶数,所以对奇偶下标分别维护一个单调队列。
PS:
队列中存储可行的队首下标,每次循环时,判断当前元素i是否可以作为队尾下标,更新答案,并将加入队列。这样避免了
的边界讨论,还保证了首尾元素相距大于等于
。
答案虽然要求输出既约分数,但仍旧可以二分答案,只要在找到可行解的时侯记录分子分母即可。
题解链接 https://www.cnblogs.com/CQzhangyu/p/7536452.html
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=2e5+5; //倍长空间
const int INF=0x3f3f3f3f;
int n,L,R;
ll A[maxn],S[maxn];
double a[maxn],s[maxn];
ll ans1,ans2;
ll gcd(ll x,ll y){
return !y?x:gcd(y,x%y);
}
int q1[maxn],q2[maxn],head1,tail1,head2,tail2;
void init(){
head1=head2=1,tail1=tail2=0;
}
bool check(double mid){
init();
for(int i=1;i<=n<<1;i++){ //倍长数组 处理环形
a[i]=(double)A[i]-mid;
s[i]=s[i-1]+a[i];
// cout<<s[i]<<" ";
}
// E;
q1[++tail1]=0;
for(int i=L;i<=n<<1;i++){ //从第L个开始考虑 避免边界的讨论
while(head1<=tail1&&i-q1[head1]>R) head1++;
while(head2<=tail2&&i-q2[head2]>R) head2++;
if(!(i&1)&&head1<=tail1&&s[i]-s[q1[head1]]>=0){ //(q1[head1],i]
ans1=S[i]-S[q1[head1]],ans2=i-q1[head1];
ll d=gcd(ans1,ans2);
ans1/=d,ans2/=d;
// D(i);D(q1[head1]);E;
return true;
}
if((i&1)&&head2<=tail2&&s[i]-s[q2[head2]]>=0){ //(q2[head2],i]
ans1=S[i]-S[q2[head2]],ans2=i-q2[head2];
ll d=gcd(ans1,ans2);
ans1/=d,ans2/=d;
// D(i);D(q2[head2]);E;
return true;
}
//入队的元素是i-L+1,这就保证队列中首尾元素差大于等于L,即队列中存储可行的队首
if(!(i-L+1&1)){
while(head1<=tail1&&s[i-L+1]<=s[q1[tail1]]) tail1--;
q1[++tail1]=i-L+1;
}
else{
while(head2<=tail2&&s[i-L+1]<=s[q2[tail2]]) tail2--;
q2[++tail2]=i-L+1;
}
}
return false;
}
int main() {
// ios::sync_with_stdio(false);
//freopen("BZOJ3316.in","r",stdin);
scanf("%d%d%d",&n,&L,&R);
double l,r;
for(int i=1;i<=n;i++){
scanf("%lld",&A[i]);
A[i+n]=A[i];
l=min(l,(double)A[i]);
r=max(r,(double)A[i]);
}
for(int i=1;i<=n<<1;i++) S[i]=S[i-1]+A[i];
/*
for(int j=1;j<=n<<1;j++) cout<<A[j]<<" ";
E;
*/
for(int i=1;i<=50;i++){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
if(ans2==0||ans2==1) printf("%lld",ans1);
else printf("%lld/%lld",ans1,ans2);
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
BZOJ4476
题意
给定长度为的序列,可以选出一个长度在
间的区间,得到
的贡献。求最大贡献。
题解
可以发现一定是最大值和最小值都在两端时最优,但可能长度超出限制。
答案可能由两部分组成:
1.长度为
2.最大值和最小值在两端
第一种情况是单调队列模板。(开两个队列,分别维护区间最大值和最小值)
第二种情况是个经典的分数规划。
二分答案,设最小值为
,最大值为
,所以式子就变成:
或
然后分开讨论,第一个变成判断的最大值是否
,第二个变成判断
的最大值是否
。
于是用单调队列维护第一个式子中的最小值和第二个式子中
的最大值,然后判断对应的表达式最大值是否
即可。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iomanip>
#include<ctime>
#include<string>
#include<bitset>
#define D(x) cout<<#x<<" = "<<x<<" "
#define E cout<<endl
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int maxn=50000+5;
const int INF=0x3f3f3f3f;
const double eps=1e-7;
int T;
int n,k,L,R;
double a[maxn];
double ans1,ans2;
int head1,tail1,head2,tail2,q1[maxn],q2[maxn];
void init(){
ans1=0,ans2=0;
}
void init1(){
head1=head2=1,tail1=tail2=0;
}
int head3,tail3,q3[maxn];
void init2(){
head3=1,tail3=0;
}
void solve1(){
init1();
for(int i=1;i<=n;i++){
while(head1<=tail1&&i-q1[head1]>=L) head1++;
while(head2<=tail2&&i-q2[head2]>=L) head2++;
while(head1<=tail1&&a[q1[tail1]]<a[i]) tail1--;
while(head2<=tail2&&a[q2[tail2]]>a[i]) tail2--;
q1[++tail1]=i;
q2[++tail2]=i;
ans1=max(ans1,(a[q1[head1]]-a[q2[head2]])/(L-1+k));
}
// D(ans1);E;
}
double temp[maxn];
bool check(double mid){
//5 3 1 2 4
// D(mid);
double res=-1; //这里不能初始化为0 否则会有误差 造成WA
init2();
for(int i=1;i<=n;i++) temp[i]=a[i]-i*mid;
for(int i=L+1;i<=n;i++){
while(head3<=tail3&&i-q3[head3]>=R) head3++;
while(head3<=tail3&&temp[q3[tail3]]>=temp[i-L]) tail3--;
q3[++tail3]=i-L;
res=max(res,temp[i]-temp[q3[head3]]);
}
// for(int i=1;i<=n;i++) cout<<temp[i]<<" ";
// D(res);
init2();
for(int i=1;i<=n;i++) temp[i]=a[i]+i*mid;
/*
for(int i=n-L;i;i--){
while(head3<=tail3&&q3[head3]-i>=R) head3++;
while(head3<=tail3&&temp[q3[tail3]]>=temp[i+L]) tail3--;
q3[++tail3]=i+L;
res=max(res,temp[i]-temp[q3[head3]]);
}
*/
for(int i=L+1;i<=n;i++){
while(head3<=tail3&&i-q3[head3]>=R) head3++;
while(head3<=tail3&&temp[q3[tail3]]<=temp[i-L]) tail3--;
q3[++tail3]=i-L;
res=max(res,temp[q3[head3]]-temp[i]);
}
// for(int i=1;i<=n;i++) cout<<temp[i]<<" ";
// D(res);E;
return res>=k*mid;
}
void solve2(){
double l=0,r=1e3;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
ans2=l;
// D(ans2);E;
}
int main() {
// ios::sync_with_stdio(false);
//freopen("BZOJ4476.in","r",stdin);
scanf("%d",&T);
while(T--){
init();
scanf("%d%d%d%d",&n,&k,&L,&R);
for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
solve1();
solve2();
printf("%.4lf\n",max(ans1,ans2));
}
return 0;
}
#endif
#ifdef method_2
/*
*/
#endif
#ifdef method_3
/*
*/
#endif
网友评论