LGP9871 [NOIP 2023] 天天爱打卡 学习笔记
Luogu Link
前言
经典题了属于是。写 \(\texttt{LGP12581}\) 时特此来回顾。
当年是有多么糖啊。希望现在只有棒棒没有糖吧。
题意简述
小 \(\text{T}\) 同学非常热衷于跑步。为了让跑步更加有趣,他决定制作一款叫做《天天爱打卡》的软件,使得用户每天都可以进行跑步打卡。开发完成后,他找来大 \(\text{Y}\) 同学试运行。
大 \(\text{Y}\) 有一个属性:能量值(初始为 \(0\))。试运行共 \(n\) 天,每天大 \(\text{Y}\) 可以选择打卡与否。若打卡,他的能量值减少 \(d\),允许减到负数。特别地,他不能连着打卡超过 \(k\) 天。
软件中又有 \(m\) 个挑战,每个形如 \((x_i,y_i,v_i)\),表示:若第 \(x_i\) 天时大 \(\text{Y}\) 已经连续打卡了 \(y_i\) 天以上,小 \(\text{T}\) 同学就会请他吃饭,从而使他的能量值提高 \(v_i\)。
试最大化 \(n\) 天结束后大 \(\text{Y}\) 的能量值。
多测。\(T\le 10\),\(1\le k,n\le 10^9\),\(1\le m\le 10^5\),\(1\le d,v_i\le 10^9\)。
做法解析
嗯首先你写个暴力 \(\texttt{DP}\) 出来。这人干的事就只有打卡和不打卡两种了,而且收益与限制的计算都与连续段有关(收益是要你的限制段能覆盖住它的时间段,限制相当于你不能有超过 \(k\) 的打卡连续段),你能不会这个暴力 \(\texttt{DP}\) 你是这个。
所以你显然可以设 \(dp_i\) 为考虑前 \(i\) 天时的最大收益,然后设 \(f_{i,j}\) 考虑前 \(i\) 天且 \([j,i]\) 打卡的最大收益。转移是显然的:\(dp_i=\max(dp_{i-1},\max_{j=i-k+1}^i f_{i,j})\),\(f_{i,j}=w_{[j,i]}+dp_{j-2}-d\times(i-j+1)\)。其中 \(w_{[j,i]}\) 为 \([j,i]\) 打卡时完成挑战的收益。
暴力做是 \(O(nkm)\),\(O(n^2)\),\(O(m^2)\) 或者其它什么类似这个复杂度的。这不重要。
重要的是,你应当发现 \(f_{i-1,j}\) 可以转移到 \(f_{i,j}\),且这个过程是很能优化的。首先,多跑一天步会因耗能带来 \(-d\) 的贡献。然后,我们理所当然把所有挑战挂在右端点,这样 \(\forall j\in (x-y,x]\),\(f_j\) 就得到 \(v\) 的贡献。这就转移完了。一个全局改一个区间改,能忍住不把 \(f_i\) 以 \(j\) 为下标摊到线段树上维护你是这个。所以这道题就做完了。
不过,这个题 \(n\) 和 \(m\) 的值域差异要求你离散化,略过考虑一些完全不必要的决策点。我们每个跑步连续段不起终于挑战的起点或终点总是不优秀的。这里离散化还是很重要的。
注意,离散化之后,除非离散化后的两个天数差仍为 \(1\),或者 \(j=1\),否则 \(f_{i,j}\) 转移中 \(dp_{j-2}\) 应改为 \(dp_{j-1}\)。
对就这样。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxM=1e5+5;
const lolo Inf=1e18;
int N,M,K,X,Y,Z,B[MaxM<<1],nln;lolo D;
struct anob{int l,r,v;}A[MaxM];
bool cmpr(anob a,anob b){return a.r<b.r;}
struct SegTree{lolo mx[MaxM<<3],tag[MaxM<<3];int cl[MaxM<<3],cr[MaxM<<3],cmid[MaxM<<3];int ls(int u){return u<<1;}int rs(int u){return (u<<1)|1;}void build(int u,int l,int r){mx[u]=tag[u]=0,cl[u]=l,cr[u]=r;if(l==r)return;int mid=(l+r)>>1;cmid[u]=mid;build(ls(u),l,mid),build(rs(u),mid+1,r);}void pushup(int u){mx[u]=max(mx[ls(u)],mx[rs(u)]);}void maketag(int u,lolo x){mx[u]+=x,tag[u]+=x;}void pushdown(int u){if(tag[u])maketag(ls(u),tag[u]),maketag(rs(u),tag[u]),tag[u]=0;}void update(int u,int dl,int dr,lolo x){if(dl<=cl[u]&&cr[u]<=dr){maketag(u,x);return;}pushdown(u);if(dl<=cmid[u])update(ls(u),dl,dr,x);if(dr>cmid[u])update(rs(u),dl,dr,x);pushup(u);}lolo getmax(int u,int dl,int dr){if(dl<=cl[u]&&cr[u]<=dr)return mx[u];pushdown(u);lolo res=-Inf;if(dl<=cmid[u])maxxer(res,getmax(ls(u),dl,dr));if(dr>cmid[u])maxxer(res,getmax(rs(u),dl,dr));return res;}
}SgT;
lolo dp[MaxM<<1];
void mian(){readis(N,M,K,D);for(int i=1;i<=M;i++){readis(X,Y,Z);if(Y>K)continue;A[i]={X-Y+1,X,Z},B[i]=X-Y+1,B[i+M]=X;}sort(A+1,A+M+1,cmpr);sort(B+1,B+M*2+1),nln=unique(B+1,B+M*2+1)-(B+1);for(int i=1;i<=M;i++)A[i].l=lwberi(B,nln,A[i].l),A[i].r=lwberi(B,nln,A[i].r);SgT.build(1,1,nln);for(int i=1,p=1,lb=1;i<=nln;i++){int lpos=(B[i]!=B[i-1]+1||i==1)?i-1:i-2;SgT.update(1,i,i,dp[lpos]);if(i>1)SgT.update(1,1,i-1,-D*(B[i]-B[i-1]));SgT.update(1,i,i,-D);for(;p<=M&&A[p].r==i;p++)SgT.update(1,1,A[p].l,A[p].v);while(B[lb]<B[i]-K+1)lb++;dp[i]=max(dp[i-1],SgT.getmax(1,lb,i));}writil(dp[nln]);
}
int Tpn,Tcn;
int main(){readis(Tpn,Tcn);while(Tcn--)mian();return 0;
}