A. 【例题1】滑动窗口
用的线段树(雾)
#include<bits/stdc++.h>
#define inl inline
#define re register
using namespace std;
const int N=1e6+10;
struct node{
int minn,maxn;
}tree[N<<2];
int n,k,a[N];
inl void update(int k){
tree[k].maxn=max(tree[k<<1].maxn,tree[k<<1|1].maxn);
tree[k].minn=min(tree[k<<1].minn,tree[k<<1|1].minn);
}
inl void build(int k,int l,int r){
if(l==r){
tree[k].maxn=tree[k].minn=a[l];
return ;
}
int mid=l+r>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
update(k);
}
inl int query1(int k,int l,int r,int x,int y){
if(x<=l&&r<=y) return tree[k].maxn;
int mid=l+r>>1;
int m=-1e9;
if(x<=mid) m=max(m,query1(k<<1,l,mid,x,y));
if(y>mid) m=max(m,query1(k<<1|1,mid+1,r,x,y));
return m;
}
inl int query2(int k,int l,int r,int x,int y){
if(x<=l&&r<=y) return tree[k].minn;
int mid=l+r>>1;
int m=1e9;
if(x<=mid) m=min(m,query2(k<<1,l,mid,x,y));
if(y>mid) m=min(m,query2(k<<1|1,mid+1,r,x,y));
return m;
}
int main(){
scanf("%d%d",&n,&k);
for(re int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
for(re int i=1;i<=n-k+1;i++){
printf("%d ",query2(1,1,n,i,i+k-1));
}
puts("");
for(re int i=1;i<=n-k+1;i++){
printf("%d ",query1(1,1,n,i,i+k-1));
}
return 0;
}
B. 【例题2】粉刷木板
运用动态规划:设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i个人刷第
j
j
j快木板
那么不难得到初始状态
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
dp[i][j]=max(dp[i−1][j],dp[i][j−1])分别表示第
i
i
i个人不刷,与
i
i
i不刷当前木板
剩下的就是在区间内逐一枚举,不妨设他粉刷的区间为
[
k
+
1
,
j
]
[k+1,j]
[k+1,j],那么就可以得到
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
k
]
+
p
i
×
(
j
−
k
)
)
dp[i][j]=max(dp[i-1][k]+p_i\times (j-k))
dp[i][j]=max(dp[i−1][k]+pi×(j−k))
但是这个
d
p
dp
dp的复杂度直接T飞,于是乎我们可以将其拆分可以得到
d
p
[
i
−
1
]
[
k
]
−
p
i
×
k
+
p
i
×
j
dp[i-1][k]-p_i\times k+p_i\times j
dp[i−1][k]−pi×k+pi×j
发现整个转移过程计算
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]时,只有k时在一直变化的,所以,我们可以单调队列维护
d
p
[
i
−
1
]
[
k
]
−
p
i
×
k
dp[i-1][k]-p_i\times k
dp[i−1][k]−pi×k的最大值
Code
#include<bits/stdc++.h>
#define inl inline
#define ll long long
#define re register
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
int n,m;
int dp[101][16010];
struct node{
int l,p,s;
}a[110];
int q[20010];
inl bool cmp(node x,node y){
return x.s<y.s;
}
int main(){
n=read(),m=read();
for(re int i=1;i<=m;i++){
a[i].l=read(),a[i].p=read(),a[i].s=read();
}
sort(a+1,a+1+m,cmp);
for(re int i=1;i<=m;i++){
int l=1,r=0;
for(re int k=max(a[i].s-a[i].l,0);k<a[i].s;k++){
while(l<=r&&dp[i-1][q[r]]-q[r]*a[i].p<=dp[i-1][k]-k*a[i].p){
r--;
}
q[++r]=k;
}
for(re int j=1;j<=n;j++){
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(j>=a[i].s){
while(l<=r&&j-q[l]>a[i].l) l++;
if(l<=r){
dp[i][j]=max(dp[i][j],(j-q[l])*a[i].p+dp[i-1][q[l]]);
}
}
}
}
printf("%d",dp[m][n]);
return 0;
}
C. 【例题3】耗费体力
这题就比较和谐了,首先可以很快得出
d
p
dp
dp方程:
d
p
[
i
]
=
d
p
[
j
]
(
a
j
>
a
i
)
,
d
p
[
i
]
=
d
p
[
j
]
+
1
(
a
j
<
=
a
i
)
dp[i]=dp[j](a_j>a_i),dp[i]=dp[j]+1(a_j<=a_i)
dp[i]=dp[j](aj>ai),dp[i]=dp[j]+1(aj<=ai)
又发现只需要维护区间最大值即可,又可以用单调队列优化
Code
#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=1e6+10;
int n,dp[N],m,a[N],q[N];
signed main(){
n=read();
for(re int i=1;i<=n;i++) a[i]=read();
m=read();
while(m--){
int x=read();
memset(dp,0,sizeof(dp));
int l=1,r=1;q[r]=1;
for(re int i=2;i<=n;i++){
while(l<=r&&i-q[l]>x) l++;
if(a[i]>=a[q[l]]) dp[i]=dp[q[l]]+1;
else dp[i]=dp[q[l]];
while(l<=r&&(dp[q[r]]>dp[i]||(dp[q[r]]==dp[i]&&a[q[r]]<a[i]))){r--;}
q[++r]=i;
}
printf("%lld\n",dp[n]);
}
return 0;
}
D. 【例题4】燃放烟火
可以设dp方程
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i个烟花燃放时在第
j
j
j个位置能获得的最大幸福值
可得dp方程:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
k
]
+
b
i
−
a
b
s
(
a
i
−
j
)
)
,
(
j
−
(
t
i
−
t
i
−
1
)
∗
d
<
=
k
<
=
j
+
(
t
i
−
t
i
−
1
)
∗
d
)
dp[i][j]=max(dp[i-1][k]+b_i-abs(a_i-j)),(j-(t_i-t_{i-1})*d<=k<=j+(t_i-t_{i-1})*d)
dp[i][j]=max(dp[i−1][k]+bi−abs(ai−j)),(j−(ti−ti−1)∗d<=k<=j+(ti−ti−1)∗d),
发现转移之前还没进行,就MLE了,但是通过观察可以发现
d
p
[
i
]
dp[i]
dp[i]的转移只依靠
d
p
[
i
−
1
]
dp[i-1]
dp[i−1]于是乎滚动数组用起来
但是发现还是会T,就在将方程拆分:得到
d
p
[
i
−
1
]
[
k
]
+
(
b
i
−
a
b
s
(
a
i
−
j
)
)
dp[i-1][k]+(b_i-abs(a_i-j))
dp[i−1][k]+(bi−abs(ai−j))发现只有
d
p
[
i
−
1
]
[
k
]
dp[i-1][k]
dp[i−1][k]在不断变化,于是可以想到找区间内的最小的
d
p
[
i
−
1
]
[
k
]
dp[i-1][k]
dp[i−1][k]
但要注意:这里的
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的含义就变了,我们为了简化题意,
a
n
s
=
∑
b
i
−
m
i
n
(
d
p
[
m
]
[
j
]
)
ans=\sum b_i-min(dp[m][j])
ans=∑bi−min(dp[m][j])
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的含义就转变为了
1
−
i
1-i
1−i次的
a
b
s
(
a
i
−
j
)
abs(a_i-j)
abs(ai−j)和的最小值,运用两次单调队列找到区间内的上一个位置的最小值即可
Code
#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
int n,m,d;
int dp[2][150010],p=0,sum,t[2];
int q[150010];
signed main(){
n=read(),m=read(),d=read();
for(re int i=1;i<=m;i++){
int a=read(),b=read();t[p^1]=read();
sum+=b;
int len=d*(t[p^1]-t[p]);
int l=1,r=0;
for(re int j=1;j<=n;j++){
while(l<=r&&q[l]<j-len) l++;
while(l<=r&&dp[p][q[r]]>dp[p][j]) r--;
q[++r]=j;
dp[p^1][j]=dp[p][q[l]]+abs(a-j);
}
l=1,r=0;
for(re int j=n;j>=1;j--){
while(l<=r&&q[l]>j+len) l++;
while(l<=r&&dp[p][q[r]]>dp[p][j]) r--;
q[++r]=j;
dp[p^1][j]=min(dp[p^1][j],dp[p][q[l]]+abs(a-j));
}
p^=1;
}
int tmp=1e18;
for(re int i=1;i<=n;i++){
tmp=min(tmp,dp[p][i]);
}
printf("%lld",sum-tmp);
return 0;
}
E. 【例题5】跳房子
首先时很明显的二分,设
d
p
[
i
]
dp[i]
dp[i]为跳到第
i
i
i个格子的最大的分
很明显的单调队列维护:维护符合范围的dp最大值,直接用
d
p
[
i
]
=
m
a
x
d
p
[
j
]
+
a
[
i
]
dp[i]=max{dp[j]}+a[i]
dp[i]=maxdp[j]+a[i]来转移
Code
#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=5e5+10;
int n,d,k;
int a[N],b[N];
int now,dp[N];
inl bool check(int x){
memset(dp,0,sizeof(dp));
now=0;
deque<int> q;
int lg=max(1ll,d-x),rg=d+x;
for(re int i=1;i<=n;i++){
while(a[now]+lg<=a[i]){
while(!q.empty()&&dp[q.back()]<dp[now]) q.pop_back();
q.push_back(now);
now++;
}
while(!q.empty()&&a[q.front()]+rg<a[i]) q.pop_front();
if(!q.empty()) dp[i]=dp[q.front()]+b[i];
else dp[i]=-1e18;
if(dp[i]>=k) return 1;
}
return 0;
}
signed main(){
n=read(),d=read(),k=read();
for(re int i=1;i<=n;i++){
a[i]=read(),b[i]=read();
}
int l=0,r=a[n];
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld",l==a[n]?-1:l);
return 0;
}
F. 1.最大矩阵
link
其实就是一个路径压缩
Code
#include<bits/stdc++.h>
#define inl inline
#define int long long
#define re register
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=1e5+10;
int n,h[N],last[N],nxt[N];
signed main(){
n=read();
for(re int i=1;i<=n;i++) h[i]=read();
last[1]=0;
for(re int i=2;i<=n;i++){
int x=i-1;
while(x&&h[x]>=h[i]) x=last[x];
last[i]=x;
}
nxt[n]=n+1;
for(re int i=n-1;i>=1;i--){
int x=i+1;
while(x<=n&&h[x]>=h[i]) x=nxt[x];
nxt[i]=x;
}
int ans=0;
for(re int i=1;i<=n;i++){
ans=max(ans,(nxt[i]-last[i]-1)*h[i]);
}
printf("%lld",ans);
return 0;
}
G. 2.最大指数和
发现可以用线段树来维护
[
i
−
R
i
−
L
]
[i-R~i-L]
[i−R i−L]的最大值,最后路径输出时直接暴力dfs即可
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls l,mid,rt<<1
#define rs mid+1,r,rt<<1|1
using namespace std;
const int N=200005;
int tree[N<<2],n,l,r,f[N<<1],a[N<<1];
void pushup(int rt)
{
tree[rt]=max(tree[rt<<1],tree[rt<<1|1]);
}
void build(int k,int num,int l,int r,int rt)
{
if(l==r&&l==k)
{
tree[rt]=num;
return ;
}
int mid=(l+r)>>1;
if(k<=mid) build(k,num,ls);
else build(k,num,rs);
pushup(rt);
}
void Init()
{
memset(f,0,sizeof(f));
memset(tree,-1e9,sizeof(tree));
memset(a,0,sizeof(a));
scanf("%d%d%d",&n,&l,&r);
for(int i=0;i<=n;i++)
{
scanf("%d",&a[i]);
}
}
int query(int le,int ri,int l,int r,int rt)
{
if(le<=l&&ri>=r)
{
return tree[rt];
}
int mid=(l+r)>>1;
if(ri<=mid)
{
return query(le,ri,ls);
}
if(le>mid)
{
return query(le,ri,rs);
}
if(le<=mid&&ri>mid)
{
return max(query(le,mid,ls),query(mid+1,ri,rs));
}
}
void dfs(int k)
{
if(k==0)
{
printf("0 ");
return ;
}
for(int i=max(k-r,0);i<=k-l;i++)
{
if(f[i]+a[k]==f[k])
{
int t1=a[i],t2=f[i];
dfs(i);
break;
}
}
if(k>=n)
{
printf("-1");
return ;
}
printf("%d ",k);
return ;
}
void work()
{
f[0]=a[0];
build(0,f[0],0,n+r,1);
for(int i=l;i<=n+r;i++)
{
f[i]=query(max(0,i-r),i-l,0,n+r,1)+a[i];
build(i,f[i],0,n+r,1);
}
int maxn=0,ord;
for(int i=n;i<=n+r;i++)
{
if(f[i]>maxn)
{
maxn=f[i];
ord=i;
}
}
printf("%d\n",maxn);
dfs(ord);
}
int main()
{
Init();
work();
return 0;
}
H. 3.最小间隔
还是二分,单调队列维护的是区间最小值,最后判断的时候要从n-x开始
Code
#include<bits/stdc++.h>
#define ll long long
#define re register
#define inl inline
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=5e4+10;
int n,a[N],q[N],t;
int dp[N];
bool check(int x){
int L=1,R=1;
memset(dp,0,sizeof(dp));
for(re int i=1;i<=n;i++){
while(L<=R&&q[L]+x+1<i) L++;
dp[i]=dp[q[L]]+a[i];
while(L<=R&&dp[q[R]]>=dp[i]) R--;
q[++R]=i;
}
for(re int i=n-x;i<=n;i++){
if(dp[i]<=t) return true;
}
return false;
}
int main(){
n=read(),t=read();
for(re int i=1;i<=n;i++) a[i]=read();
int l=0,r=n;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d\n",r);
return 0;
}
I. 4.写博客
思路 每一个子串用KMP跑一遍,记录一下每一个位置之前的最近的违禁单词起始位置
设dpi为第i个位置必须删掉,能得到的最小减少量,那么答案就是dp[n+1]
那么用单调队列维护维护的范围也有了,那么就直接开干就行了!
#include<bits/stdc++.h>
#define ll long long
#define re register
#define inl inline
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=2e5+10;
int n,m,nxt[N],a[N],r[N],dp[N],q[N],l,rr;
char s[N],t[N];
int main(){
n=read(),m=read();
scanf("%s",s+1);
for(re int i=1;i<=n;i++) a[i]=read();
while(m--){
scanf("%s",t+1);
int len=strlen(t+1),j=0;
for(re int i=2;i<=len;i++){
while(j&&t[j+1]!=t[i]) j=nxt[j];
if(t[j+1]==t[i]) j++;
nxt[i]=j;
}
j=0;
for(re int i=1;i<=n;i++){
while(j&&t[j+1]!=s[i]) j=nxt[j];
if(t[j+1]==s[i]) j++;
if(j==len){
r[i]=max(r[i],i-len+1);
}
}
}
for(re int i=1;i<=n+1;i++) r[i]=max(r[i],r[i-1]);
l=1,rr=1;
for(re int i=1;i<=n+1;i++){
while(l<=rr&&q[l]<r[i-1]) l++;
dp[i]=dp[q[l]]+a[i];
while(l<=rr&&dp[q[rr]]>=dp[i]) rr--;
q[++rr]=i;
}
printf("%lld\n",dp[n+1]);
return 0;
}
J. 5.出题方案
首先用前缀和来维护区间
用dp[i],来维护[1-n]的最小值
#include<bits/stdc++.h>
#define int long long
#define re register
#define inl inline
using namespace std;
int read(){
int sum=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
while(isdigit(c)){sum=(sum<<3)+(sum<<1)+(c^48);c=getchar();}
return sum*f;
}
const int N=3e5+10;
template <typename T> struct que{
T a[N],st=1,ed=0;
void dque(){st=1,ed=0;}
inl void clear(){st=1,ed=0;}
inl int size(){return ed-st+1;}
inl bool empty(){return !(ed-st+1);}
inl void pop_front(){st++;}
inl void pop_back(){ed--;}
inl T front(){return a[st];}
inl T back(){return a[ed];}
inl void push_back(T x){a[++ed]=x;}
inl T operator [] (int x){return a[st+x-1];}
};
int n,m;
int dp[N],a[N],sum[N];
inl int calc(int l,int r){
return l>r ? -1e18 : sum[r]-sum[l-1];
}
multiset<int> s;
que<int> q;
signed main(){
memset(dp,0x3f3f3f3f,sizeof(dp));
n=read(),m=read();
for(re int i=1;i<=n;i++){
a[i]=read();
sum[i]=sum[i-1]+a[i];
}
dp[0]=0;
int j=0;
q.push_back(0);
for(re int i=1;i<=n;i++){
while(calc(j+1,i)>m) j++;
while(!q.empty()&&q.front()<j){
if(q.size()>=2) s.erase(s.find(dp[q[1]]+a[q[2]]));
q.pop_front();
}
while(!q.empty()&&a[q.back()]<a[i]){
if(q.size()>=2) s.erase(s.find(dp[q[q.size()-1]]+a[q[q.size()]]));
q.pop_back();
}
if(!q.empty()) s.insert(dp[q.back()]+a[i]);
q.push_back(i);
dp[i]=dp[j]+a[q.front()];
if(q.size()>=2) dp[i]=min(dp[i],*s.begin());
}
printf("%lld",dp[n]);
return 0;
}