[NOIP2023] 天天爱打卡 题解
[NOIP2023] 天天爱打卡 题解
知识点
动态规划,动态规划优化,线段树。
题意简述
总共 \(n\) 天,每天可以选择跑步或不跑,跑步会花费 \(d\) 单位能量,能量可以变为负数。
有 \(m\) 个奖励,每个形如 \((x_i,y_i,v_i)\),代表到第 \(x_i\) 天,如果跑步连续了 \(y_i\) 天,可以获得 \(v_i\) 点能量。
问 \(n\) 天后最多能剩下多少能量。
分析
这一道决策类型的题目,乍看范围似乎要用贪心,但是一看题意又无从下手,那么考虑动态规划。
设 \(f_i\) 表示第 \(i\) 天没跑的最大剩余值,那么有转移式:
\[\begin{aligned}
f_i = \max_{j < i - 1}{\{ f_j + (i-j-1)d + \sum_{k=1}^m v_k [j < x_k - y_k + 1 \land x_k < i] \}} \\
\end{aligned}
\]
表示包含在 \([j+1,i-1]\) 中的这些天都有跑,而第 \(j\) 天没跑,加和的最大值。
我们发现其实有改变的天数只是那些有可能成为 \(x_i+1\) 或 \(x_i-y_i\) 的点,就是一个奖励形成的区间的左边和右边,那么我们将他们离散化下来即可。
再看转移式,一个显然的套路是把变量和不变量分离:
\[\begin{aligned}
f_i = \max_{j < i - 1}{\{ f_j + (i-j-1)d + \sum_{k=1}^m v_k [j < x_k - y_k + 1 \land x_k < i] \}} \\
(f_i - id) = \max_{j < i - 1}{\{ (f_j - jd) -d + \sum_{k=1}^m v_k [j < x_k - y_k + 1 \land x_k < i] \}} \\
\end{aligned}
\]
此时我们就可以把离散后的点存到线段树上,不过第 \(i\) 个点记的是 \(f_i - id\),因为这样方便转移。
然后尺取一下,就变成了线段树优化 DP 板子题。
代码
时间复杂度:\(O(Tm\log_2{m})\),空间复杂度:\(O(m)\)。
#define Plus_Cat "run"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
namespace DEBUG {
#define DE(...) _Debug(#__VA_ARGS__,__VA_ARGS__)
template<class T>void _Debug(const char *format,T t) {
return cerr<<format<<':'<<t<<'.'<<endl,void();
}
template<class T,class... Types>void _Debug(const char *format,T a,Types... args) {
while(*format!=',')cerr<<*format++;
return cerr<<':'<<a<<", ",_Debug(format+1,args...);
}
} using namespace DEBUG;
namespace IOstream {
#define getc() getchar()
#define putc(ch) putchar(ch)
#define isdigit(ch) ('0'<=(ch)&&(ch)<='9')
template<class T>void rd(T &x) {
static char ch(0);
for(x=0,ch=getc(); !isdigit(ch); ch=getc());
for(; isdigit(ch); x=(x<<1)+(x<<3)+(ch^48),ch=getc());
}
} using namespace IOstream;
constexpr int N(1e9+10),M(1e5+10);
int ID,Cas,n,m,k,d;
ll ans;
struct DCT {
int b[M<<1];
int &operator [](int i) {
return b[i];
}
int operator ()(int i) {
return pos(i);
}
void Clear() {
b[0]=0;
}
void push(int x) {
b[++b[0]]=x;
}
void Solve() {
sort(b+1,b+b[0]+1),b[0]=unique(b+1,b+b[0]+1)-b-1;
}
int pos(int x) {
return lower_bound(b+1,b+b[0]+1,x)-b;
}
} dct;
struct Change {
int l,r,v;
Change(int l=0,int r=0,int v=0):l(l),r(r),v(v) {}
friend bool operator <(const Change &a,const Change &b) {
return a.r^b.r?a.r<b.r:a.l<b.l;
}
void Scan() {
rd(r),rd(l),rd(v),l=r-l+1,dct.push(l-1),dct.push(r+1);
}
int Solve() {
return l=dct(l-1),r=dct(r+1);
}
} q[M];
struct SEG {
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
struct node {
ll mx,tag;
node(ll mx=0,ll tag=0):mx(mx),tag(tag) {}
void down(ll _tag) {
mx+=_tag,tag+=_tag;
}
} tr[M<<3];
void Up(int p) {
tr[p].mx=max(tr[ls].mx,tr[rs].mx);
}
void Down(int p) {
if(tr[p].tag)tr[ls].down(tr[p].tag),tr[rs].down(tr[p].tag),tr[p].tag=0;
}
void Build(int p=1,int l=1,int r=dct[0]) {
tr[p]=node((ll)dct[r]*::d);
if(l==r)return;
Build(ls,l,mid),Build(rs,mid+1,r);
}
void Plus(int L,int R,ll d,int p=1,int l=1,int r=dct[0]) {
if(L<=l&&r<=R)return tr[p].down(d);
Down(p);
if(L<=mid)Plus(L,R,d,ls,l,mid);
if(mid<R)Plus(L,R,d,rs,mid+1,r);
Up(p);
}
ll Max(int L,int R,int p=1,int l=1,int r=dct[0]) {
if(L<=l&&r<=R)return tr[p].mx;
Down(p);
if(R<=mid)return Max(L,R,ls,l,mid);
if(mid<L)return Max(L,R,rs,mid+1,r);
return max(Max(L,R,ls,l,mid),Max(L,R,rs,mid+1,r));
}
#undef ls
#undef rs
#undef mid
} seg;
int Cmain() {
rd(n),rd(m),rd(k),rd(d),dct.Clear(),ans=0;
FOR(i,1,m)q[i].Scan();
dct.Solve(),seg.Build();
FOR(i,1,m)q[i].Solve();
sort(q+1,q+m+1);
int pos(1),it(1);
FOR(i,2,dct[0]) {
while(pos<=dct[0]&&dct[i]-dct[pos]-1>k)++pos;
while(it<=m&&q[it].r==i)seg.Plus(1,q[it].l,q[it].v),++it;
if(pos<i)tomax(ans,seg.Max(pos,i-1)-1ll*(dct[i]-1)*d);
seg.Plus(i,i,ans);
}
printf("%lld\n",ans);
return 0;
}
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
for(rd(ID),rd(Cas); Cas; --Cas)Cmain();
return 0;
}
反思
- 思路凌乱,考试时没有提前看这题。
- 看完题目后想的方法较麻烦。

浙公网安备 33010602011771号