单调栈/单调队列训练记录 2025.2
大概套路:
条件:问题可以拆成一些部分之和的最值,且互不干涉,转移范围是一定的,而且要么可直接转移,要么具有可差分性
Cashback
https://www.gxyzoj.com/d/hzoj/p/4376
结论只推到一定是
因为长度变长,最小值可能变小,所以可以直接分为两种情况
记当前点去掉的数之和最大为
对于长度恰好为
Cutlet
https://www.gxyzoj.com/d/hzoj/p/4382
这道题有两维限制,朴素的,设
但是注意到,不属于区间的点是可以统一计算的,所以只用考虑区间内
又注意到,其实翻 0,1,2 次就是所有的情况了,因为可以把不与前后相连的同一面朝下时间连一起
所以将
但是背包转移的时间复杂度大,因为是连续的,考虑单调队列
0 直接转移,2 因为朝上面不变,所以只考虑加上了多少
1 比较复杂,假设区间长度为
注意,这里
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,k,l[105],r[105],q[200005],f[105][200005],tail,head;
int main()
{
scanf("%d%d",&n,&k);
n=n*2;
for(int i=1;i<=k;i++)
{
scanf("%d%d",&l[i],&r[i]);
}
for(int i=0;i<=k;i++)
{
for(int j=0;j<=n;j++)
{
f[i][j]=1e9;
}
}
f[0][0]=0;
for(int i=1;i<=k;i++)
{
for(int j=0;j<=n;j++)
{
f[i][j]=f[i-1][j];
}
head=1,tail=0;
int x=r[i]-l[i];
for(int j=0;j<=r[i];j++)
{
if(head<=tail&&q[head]<j-x) head++;
while(head<=tail&&f[i-1][q[tail]]>f[i-1][j]) tail--;
q[++tail]=j;
f[i][j]=min(f[i][j],f[i-1][q[head]]+2);
}
head=1,tail=0;
for(int j=r[i];j>=0;j--)
{
if(head<=tail&&q[head]<r[i]-j-x) head++;
int tmp=r[i]-j;
while(head<=tail&&f[i-1][q[tail]]>f[i-1][tmp]) tail--;
q[++tail]=tmp;
f[i][j]=min(f[i][j],f[i-1][q[head]]+1);
}
}
if(f[k][n/2]!=1e9)
printf("Full\n%d",f[k][n/2]);
else printf("Hungry");
return 0;//
}
Kuzya and Homework
https://www.gxyzoj.com/d/hzoj/p/4378
看到乘除,考虑分解质因数处理,将乘记为正,除记为负
而题目中满足条件的区间,就是每个质数的次数和都是自然数
但是这样很难维护,不具有单调性
我们可以记
是乘时就是
所以每次计算乘时,每个因子都往对应的栈里压入相应数量的下标,除时弹出即可
如果一个区间
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6;
int n,a[N+5],vis[N+5],p[N+5],f[N+5],cnt,b[N+5];
int l[N+5];
void init()
{
for(int i=2;i<=N;i++)
{
if(!vis[i])
{
p[++cnt]=i,f[i]=i;
}
for(int j=1;p[j]*i<=N;j++)
{
vis[i*p[j]]=1,f[i*p[j]]=p[j];
if(i%p[j]==0) break;
}
}
}
string s;
vector<int> st[N+5];
struct node{
int id,val;
};
stack<node> s1;
void add(int v,int x)
{
l[x]=x;
while(v>1)
{
st[f[v]].push_back(x);
v/=f[v];
}
}
void del(int v,int x)
{
l[x]=x;
while(v>1)
{
if(st[f[v]].size()==0)
{
l[x]=0;
break;
}
l[x]=min(l[x],st[f[v]].back());
st[f[v]].pop_back();
v/=f[v];
}
}
int main()
{
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
cin>>s;
s=" "+s;
for(int i=1;i<=n;i++)
{
if(s[i]=='*') b[i]=1;
}
for(int i=1;i<=n;i++)
{
if(b[i]) add(a[i],i);
else del(a[i],i);
// printf("%d ",b[i]);
}
ll ans=0;
for(int i=n;i>0;i--)
{
int now=1;
while(!s1.empty()&&s1.top().id>=i)
{
now+=s1.top().val;
s1.pop();
}
if(l[i]==i) ans+=now;
s1.push((node){l[i],now});
// printf("%d ",l[i]);
}
printf("%lld",ans);
return 0;
}
Non-equal Neighbours
https://www.gxyzoj.com/d/hzoj/p/CF1585F
反正是完全跑在了另一条路上
看到这种计数,大概率是容斥,观察发现,有
因为是容斥,符号是按奇偶性变化的,而且段数一样,所以考虑至少被分成的段数是奇数/偶数的情况
设
考虑转移,朴素的方程显然是
考虑优化,可以发现,这里的取 min 具有单调性,而且因为是求和,可差分
所以可以将当前
这里记单调栈中
所以式子是
后面部分前缀和即可
点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
const int mod=998244353;
int n,a[200005],top,st[200005];
ll f[200005][2],s[200005][2];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
f[0][0]=s[0][0]=1;
for(int i=1;i<=n;i++)
{
while(top&&a[st[top]]>=a[i]) top--;
st[++top]=i;
f[i][0]=((top==1?0:f[st[top-1]][0])+(s[i-1][1]-(top==1?0:s[st[top-1]-1][1])+mod)*a[i]%mod)%mod;
f[i][1]=((top==1?0:f[st[top-1]][1])+(s[i-1][0]-(top==1?0:s[st[top-1]-1][0])+mod)*a[i]%mod)%mod;
s[i][0]=(s[i-1][0]+f[i][0])%mod;
s[i][1]=(s[i-1][1]+f[i][1])%mod;
}
printf("%lld",((f[n][0]-f[n][1])*(n%2?mod-1:1)%mod+mod)%mod);
return 0;
}
「CZOI-R1」消除威胁
https://www.gxyzoj.com/d/hzoj/p/4381
反正是读错题了
因为是正负性上操作,所以可以一开始全弄成正的,先考虑如果一个区间内值为
关键在于这个区间,单调栈处理后排序即可
蓬莱「凯风快晴 −富士火山−」
https://www.gxyzoj.com/d/hzoj/p/4383
有两个性质,最深的选择层一定是全选,最优情况下一定选了根
如果将每一层的点数弄成一个数组,假设当前层数为
所以,单调栈找第一个大于等于当前点的数,直接转移就行了
点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
int n,edgenum,head[500005],f[500005],s[500005],top;
struct edge{
int to,nxt;
}e[1000005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int dep[500005],cnt[500005];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
cnt[dep[u]]++;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
f[1]=1,s[1]=1,top=1;
int ans=0;
for(int i=2;i<=n;i++)
{
// printf("%d ",cnt[i]);
if(!cnt[i]) break;
while(top&&cnt[s[top]]>cnt[i]) top--;
f[i]=f[s[top]]+cnt[i]*(i-s[top]);
s[++top]=i;
ans=max(ans,f[i]);
}
printf("%d",ans);
return 0;
}
[HNOI2016] 序列
https://www.gxyzoj.com/d/hzoj/p/4384
实在是抽象,看到区间求值,不好线段树,考虑莫队
它的难点在于如何加点和删点,也就是如何计算类似于右端点
显然的,在区间最小值前的点的贡献就是区间最小值,可以ST表
剩下部分可以想到差分,在预处理的时候,可以单调栈,每次记录的是以当前的为终点的贡献,因为是从第一个小于等于当前点的位置转移,直接相减即可
点击查看代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,a[100005],st[100005][20],blen,l1[100005],pos[100005];
int top,s[100005],f[100005][20];
ll gr[100005],gl[100005],ans[100005];
struct node{
int l,r,id;
}q[100005];
void init()
{
blen=sqrt(n);
l1[0]=-1;
for(int i=1;i<=n;i++)
{
st[i][0]=a[i],pos[i]=i/blen,l1[i]=l1[i>>1]+1,f[i][0]=i;
}
for(int i=1;i<=l1[n];i++)
{
for(int j=1;j+(1<<i)-1<=n;j++)
{
int x=j+(1<<(i-1));
if(st[j][i-1]<st[x][i-1])
st[j][i]=st[j][i-1],f[j][i]=f[j][i-1];
else st[j][i]=st[x][i-1],f[j][i]=f[x][i-1];
}
}
}
int query(int l,int r)
{
int x=l1[r-l+1];
if(st[l][x]<st[r-(1<<x)+1][x]) return f[l][x];
else return f[r-(1<<x)+1][x];
}
bool cmp(node x,node y)
{
if(pos[x.l]==pos[y.l]) return x.r<y.r;
return x.l<y.l;
}
ll getl(int l,int r)
{
int p=query(l-1,r);
return 1ll*a[p]*(r-p+1)+gl[l-1]-gl[p];
}
ll getr(int l,int r)
{
int p=query(l,r+1);
// printf("%d %d %d\n",l,r,p);
return 1ll*a[p]*(p-l+1)+gr[r+1]-gr[p];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
init();
for(int i=1;i<=m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
q[i]=(node){l,r,i};
}
sort(q+1,q+m+1,cmp);
for(int i=1;i<=n;i++)
{
// printf("%d ",pos[i]);
while(top&&a[s[top]]>=a[i]) top--;
gr[i]=gr[s[top]]+1ll*(i-s[top])*a[i];
s[++top]=i;
// printf("g%d ",gr[i]);
}
top=0,s[0]=n+1;
for(int i=n;i>0;i--)
{
while(top&&a[s[top]]>=a[i]) top--;
gl[i]=gl[s[top]]+1ll*(s[top]-i)*a[i];
s[++top]=i;
}
int l=q[1].l,r=l-1;
ll sum=0;
for(int i=1;i<=m;i++)
{
// printf("%d %d\n",q[i].l,q[i].r);
while(l>q[i].l) sum+=getl(l--,r);
while(r<q[i].r) sum+=getr(l,r++);
while(l<q[i].l) sum-=getl(++l,r);
// printf("%d\n",sum);
while(r>q[i].r) sum-=getr(l,--r);
ans[q[i].id]=sum;
}
for(int i=1;i<=m;i++)
{
printf("%lld\n",ans[i]);
}
return 0;
}
[USACO18FEB] Snow Boots G
https://www.gxyzoj.com/d/hzoj/p/4401
先离散化,排序后找每个上线的最大间距,暴力线段树即可
区区区间间间
https://www.gxyzoj.com/d/hzoj/p/3693
弱智题,暴力单调栈即可
[POI 2014] BAR-Salad Bar
https://www.gxyzoj.com/d/hzoj/p/4536
一个方向显然,看什么情况下
接下来考虑倒着,如果当前的
最后统计答案,跳到终止点即可
时间复杂度
[ARC177D] Earthquakes
https://www.gxyzoj.com/d/hzoj/p/3886
可以将原序列按位置排序后分成若干段,每段之间互不影响,此时,只需要分别看每段的概率即可
设当前点的位置是从左往右第
显然两侧先倒的不能干涉到当前位置,因为方向单一,直接数出个数相除即可
因为必须是有效的倒下,单调栈处理即可
对于剩下部分,如果要求的点是端点,才有情况
统计的时候枚举时间,可以把概率扔进线段树求乘积
点击查看代码
#include<cstdio>
#include<algorithm>
#define lid id<<1
#define rid id<<1|1
#define ll long long
using namespace std;
const int mod=998244353;
int n,h,pos[200005],idx,s[200005],top;
int f[200005],g[200005],fl[200005],mp[200005];
ll p[200005];
struct node{
int t,x;
}a[200005],b[200005];
bool cmp(node x,node y)
{
return x.x<y.x;
}
ll qpow(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void get(int st)
{
pos[st]=++idx;
int ed=n;
for(int i=st+1;i<=n;i++)
{
if(a[i].x-a[i-1].x>h)
{
ed=i-1;
break;
}
pos[i]=idx;
}
top=0;
for(int i=st;i<=ed;i++)
{
while(top&&a[s[top]].t>=a[i].t) top--;
f[i]=f[s[top]]+1;
if(s[top]==i-1||i==st) fl[i]++;
s[++top]=i;
}
top=0;
for(int i=ed;i>=st;i--)
{
while(top&&a[s[top]].t>=a[i].t) top--;
g[i]=g[s[top]]+1;
if(s[top]==i+1||i==ed) fl[i]++;
s[++top]=i;
}
for(int i=st;i<=ed;i++)
{
p[i]=qpow(2,mod-2)*fl[i]%mod;
p[i]=p[i]*qpow(2,ed-st+1-f[i]-g[i]+2)%mod;
}
}
struct seg_tr{
int l,r;
ll sum;
}tr[800008];
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
// printf("%d %d %d\n",id,l,tr[id].l);
if(l==r) return;
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
void update(int id,int x,ll val)
{
if(tr[id].l==tr[id].r)
{
tr[id].sum=(tr[id].sum+val)%mod;
return;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(x<=mid) update(lid,x,val);
else update(rid,x,val);
tr[id].sum=tr[lid].sum*tr[rid].sum%mod;
}
ll query(int id,int l,int r)
{
if(l>r) return 1;
// printf("%d %d %d %d %d\n",id,l,r,tr[id].l,tr[id].r);
if(l==tr[id].l&&tr[id].r==r)
{
return tr[id].sum;
}
int mid=(tr[id].l+tr[id].r)>>1;
if(r<=mid) return query(lid,l,r);
else if(l>mid) return query(rid,l,r);
else return query(lid,l,mid)*query(rid,mid+1,r)%mod;
}
int main()
{
scanf("%d%d",&n,&h);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
a[i]=(node){i,x};
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
mp[a[i].t]=i;
if(!pos[i]) get(i);
}
build(1,1,idx);
// for(int i=1;i<=n;i++)
// {
// printf("%d %d %d\n",f[i],g[i],p[i]);
// }
// printf("%d ",idx);
//printf("%d ",tr[1].l);
for(int i=1;i<=n;i++)
{
int x=mp[i];
// printf("%d %lld %d\n",pos[x],p[x],idx);
ll ans=query(1,1,pos[x]-1)*p[x]%mod*query(1,pos[x]+1,idx)%mod;
printf("%lld ",ans);
update(1,pos[x],p[x]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2024-02-23 20240219比赛总结
2024-02-23 20240221比赛总结
2024-02-23 20240222比赛总结
2024-02-23 BSGS学习笔记
2024-02-23 概率学习笔记
2024-02-23 期望学习笔记
2024-02-23 基环树学习笔记