[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;
}

反思

  1. 思路凌乱,考试时没有提前看这题。
  2. 看完题目后想的方法较麻烦
posted @ 2024-11-22 21:30  Add_Catalyst  阅读(90)  评论(0)    收藏  举报