题解:NOIP2023 天天爱打卡

NOIP2023 天天爱打卡 - luogu

算法一

upd :2024/10/31。

f[i] 表示第 i 天休息, 1i 天能获得的最大能量。

考虑如何从 j 转移过来,发现 j+1i1 就是连续跑步了,根据题目的限制,可得

f[i]=maxij1kf[j]+val(j+1,i1)(ij1)×d

直接做是 O(Tn2m),能获得12pts。

考虑优化掉枚举区间的O(m)。用类似双指针的方法,可以做到每个贡献区间只加入一次,不离散化有 36pts。离散化后就是 O(Tmin(n,m)2),貌似还恰好打中了特殊性质 A,因此获得了 52pts。

int solve(){
	int mx = 0;
	F(i, 0, cnt + 10) f[i] = 0, val[i] = 0;
	F(i, 1, cnt){
		sort(all(r[i]), [&](const int &p, const int &q){return e[p].x > e[q].x;});
		int nw = 0, sum = 0, maxn = r[i].size();
		f[i + 1] = f[i];
		G(j, i, 1){
			int dis = lsh.rk[i] - lsh.rk[j] + 1;
			if(dis > k) break;
			for(;nw < maxn && e[r[i][nw]].x >= j; ++ nw) sum += e[r[i][nw]].v;
			val[j] += sum;
			
			if(lsh.rk[j] - lsh.rk[j - 1] > 1) {
				f[i + 1] = max(f[i + 1], f[j] + max(0ll, val[j] - dis * d));	
			}
			else {
				f[i + 1] = max(f[i + 1], f[j - 1] + max(0ll, val[j] - dis * d));
			}
		}
		mx = max(mx, f[i + 1]);
		cerr << i + 1 << ' ' << f[i + 1] << '\n';
	}
	return mx;
}

对于固定的 i, 求的是 maxij1k(f[j]+val(j+1)+(j+1)×d)i×dmax 里面的东西可以放到线段树上维护,离散化就是 O(Tmlogm)


下面讲一下代码细节,主要是离散化之后边界条件以及各种加减 1 很难调。

我的代码中是枚举跑步的区间 (j,i),因此是从 f[j1] 转移到了 f[i+1],感觉这样清楚一些。

  • 关于 j 的下界:倍增或者二分。

  • 关于转移:i<j 时,f[i+1] 直接从 f[i] 继承答案。

  • 关于加减 1:想清楚谁转移到谁,贡献区间长度是 rk[i]rk[j]+1,不是 rk[i]rk[j+1]

  • 关于特判:由于离散化后数轴上只保留了端点,所以可能存在这样一种情况:rk[i]rk[j]+1<krk[i]rk[j1]+1>k,但实际上 rk[j]rk[j1]>1。那么此时 j1i 的转移应该是

    f[j]+val(j,i)

    而不是

    f[j1]+val(j,i)

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define int ll
using namespace std;
using ll = long long;
char buf[100], *p1 = buf, *p2 = buf;
inline int gc(){ return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100,stdin),p1==p2)?EOF:*p1++; }
inline int rd(){
	int x = 0; char ch;
	while(!isdigit(ch = gc()));
	do x = (x << 3) + (x << 1) + (ch ^ 48); while(isdigit(ch = gc()));
	return x;
}
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
struct node{
	int x, y, v;
}e[N];
int c, T, n, m, k, d, cnt = 0; 
int rk[N], f[N];
vector<int> r[N];
struct lisanhua{
	void add(int x){
		rk[++ cnt] = x;
	}
	int find(int x){
		return lower_bound(rk + 1, rk + cnt + 1, x) - rk;
	}
	void init(){
		sort(rk + 1, rk + cnt + 1);
		cnt = unique(rk + 1, rk + cnt + 1) - rk - 1;
	}
	void clear(){
		cnt = 0;
	}
}lsh;
struct Tree{
	int mx[N << 2], tag[N << 2];
	void push(int u, int val){
		tag[u] += val;
		mx[u] += val;
	}
	void pushdown(int u){
		if(tag[u]){
			push(u * 2, tag[u]);
			push(u * 2 + 1, tag[u]);
			tag[u] = 0;
		}
	}
	void pushup(int u){
		mx[u] = max(mx[u * 2], mx[u * 2 + 1]);
	}
	void update(int u, int l, int r, int x, int y, int val){
		if(x > y) return ;
		if(l >= x && r <= y){
			push(u, val);
			return ;
		} pushdown(u);
		int mid = (l + r) >> 1;
		if(x <= mid) update(u * 2, l, mid, x, y, val);
		if(y > mid) update(u * 2 + 1, mid + 1, r, x, y, val);
		pushup(u);
	}
	void change(int u, int l, int r, int x, int val){
		if(l == r) {
			mx[u] += val;
			return ;
		} pushdown(u);
		int mid = (l + r) >> 1;
		if(x <= mid) change(u * 2, l, mid, x, val);
		else change(u * 2 + 1, mid + 1, r, x, val);
		pushup(u);
	}
	int query(int u, int l, int r, int x, int y){
		if(x < 0 || y < 0 || x > y) return 0;
		if(l >= x && r <= y){
			return mx[u];
		} pushdown(u);
		int mid = (l + r) >> 1, ret = 0;
		if(x <= mid) ret = max(ret, query(u * 2, l, mid, x, y));
		if(y > mid) ret = max(ret, query(u * 2 + 1, mid + 1, r, x, y));
		return ret;
	}
	void clear(int u, int l, int r){
		mx[u] = tag[u] = 0;
		if(l == r) return ;
		int mid = (l + r) >> 1;
		clear(u * 2, l, mid);
		clear(u * 2 + 1, mid + 1, r);
	}
}tr;
void init(){
	n = rd(), m = rd(), k = rd(), d = rd();
	F(i, 1, m){
		e[i].y = rd();
		e[i].x = e[i].y - rd() + 1;
		e[i].v = rd();
		lsh.add(e[i].x);
		lsh.add(e[i].y);
	}
	lsh.init();
	F(i, 1, m) {
		e[i].x = lsh.find(e[i].x);
		e[i].y = lsh.find(e[i].y);
		r[e[i].y].push_back(i);
	}
}
int solve(){
	int mx = 0;
	tr.change(1, 0, cnt, 0, rk[1] * d);
	F(i, 0, cnt){ // run day
		for(auto j : r[i]) tr.update(1, 0, cnt, 0, e[j].x - 1, e[j].v); //区间修改
		int lx = lsh.find(rk[i] - k + 1);
//		G(j, 20, 0) if(lx >= (1 << j) && lsh.calc(lx - (1 << j), i) <= k) lx -= (1 << j);
		f[i + 1] = tr.query(1, 0, cnt, max(0ll, lx - 1), i - 1) - (rk[i] + 1) * d; //区间查询max
		f[i + 1] = max(f[i], f[i + 1]);
		if(i < cnt){
			tr.change(1, 0, cnt, i + 1, f[i + 1] + rk[i + 2] * d); //单点修改
			if(rk[i] < rk[i + 1] - 1) {
				tr.change(1, 0, cnt, i, f[i + 1] - f[i]);
			}
		}
		mx = max(mx, f[i + 1]);
	}
	return mx;
}
signed main(){
	c = rd(), T = rd();
	while(T --){
		init();
		tr.clear(1, 0, cnt + 1);
		int ret = solve();
		printf("%lld\n", ret);
		F(i, 0, cnt + 1) f[i] = 0, r[i].clear();
		lsh.clear();
	}
	return fflush(0), fclose(stdin), fclose(stdout), 0;
}

算法二

这里是另一种状态设计,线段树优化部分是类似的。

先考虑一个朴素的dp, f[i] 表示前 i 天, 第 i 天必打卡能得到的最大能量,有转移:

f[i]=maxj=ik+1i(val(j,i)d×(ij+1)+maxp=1j2f[p])

val(j,i) 表示第 ji 天完成的挑战。

直接做是 O(Tn2mk)

优化1: maxp=1j2f[p] 可以前缀 max 优化。

优化2:把每个挑战挂在它的终点上,这样每个挑战只会被访问一次。

优化3:记 g[j]=val(j,i)d×(ij+1)+maxp=1j2f[p], 那么 f[i]=maxj=ik+1ig[j]

这个东西可以用线段树优化,只需要维护区间加,区间求max。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
#define int long long
#define pii pair<int,int>
using namespace std;
using ll = long long;
const int N=2e5+105;
int c,T,n,m,d,k,cnt=0;
int x[N],y[N],v[N],g[N],rk[N];
int mx[N<<2],tag[N<<2];
vector<pii> e[N];
inline void pushup(int u){
	mx[u]=max(mx[u*2],mx[u*2+1]);
}
inline void pushdown(int p,int l,int r){
	if(tag[p]!=0){
		mx[p*2]+=tag[p];
		mx[p*2+1]+=tag[p];
		tag[p*2]+=tag[p];
		tag[p*2+1]+=tag[p];
		tag[p]=0;
	}
}
inline void update(int p,int l,int r,int x,int y,int z){
	if(x>y) return ;
	if(x<=l && r<=y){
		tag[p]+=z;
		mx[p]+=z;
		return ;
	}
	pushdown(p,l,r);
	int mid=(l+r)>>1;
	if(x<=mid) update(p*2,l,mid,x,y,z);
	if(y>mid) update(p*2+1,mid+1,r,x,y,z);
	pushup(p);
}
inline int ask(int p,int l,int r,int x,int y){
	if(x<=l && r<=y){
		return mx[p];
	} pushdown(p,l,r);
	int mid=(l+r)>>1,maxn=0;
	if(x<=mid) maxn=max(maxn,ask(p*2,l,mid,x,y));
	if(y>mid) maxn=max(maxn,ask(p*2+1,mid+1,r,x,y));
	return maxn;
}
inline int get(int x){
	return lower_bound(rk+1,rk+cnt+1,x)-rk;
}
signed main(){
//	freopen("run.in","r",stdin);
//	freopen("run.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin>>c>>T;
	while(T--){
		memset(mx,0,sizeof(mx));
		memset(tag,0,sizeof(tag));
		g[0]=0;
		cnt=0;
		cin>>n>>m>>k>>d;
		F(i,1,m) {
			cin>>x[i]>>y[i]>>v[i];
			y[i]=x[i]-y[i]+1;
			rk[++cnt]=x[i];
			rk[++cnt]=y[i];
		}
		sort(rk+1,rk+cnt+1); cnt=unique(rk+1,rk+cnt+1)-rk-1;
		F(i,1,cnt) e[i].clear(); 
		F(i,1,m){
			int xx=get(x[i]),yy=get(y[i]);
			e[xx].push_back({yy,v[i]});
		}
		F(i,1,cnt){
			if(rk[i]-rk[i-1]>1 || i==1)	update(1,1,cnt,i,i,g[i-1]-d);		
			else update(1,1,cnt,i,i,g[i-2]-d);		
			update(1,1,cnt,1,i-1,(rk[i-1]-rk[i])*d);
//			update(1,1,cnt,i,i,((i==1||rk[i]-rk[i-1]>1)?g[i-1]:g[i-2])-d);
//			if(i>1) update(1,1,cnt,1,i-1,-(rk[i]-rk[i-1])*d);
			int siz=e[i].size();
			F(j,0,siz-1){
				pii o=e[i][j];
				update(1,1,cnt,1,o.first,o.second);	
			}
			int lx = lower_bound(rk+1,rk+cnt+1,rk[i]-k+1)-rk; 
			g[i]=max(g[i-1],max(0ll,ask(1,1,cnt,lx,i)));
		}
		cout<<g[cnt]<<"\n";
	}
	return 0;
}

总结

  • 本题中的状态定义颇具特色,f[i] 表示不选第 i 天,这样就可以表示 某一段全选
  • 枚举区间的复杂度过高,要想到精确添加贡献的方法。
  • 线段树优化dp一定先写朴素dp再优化,否则很多细节根本注意不到!
  • 最后就是离散化之后修改和查询有一些细节很需要注意!(超级难调)
posted @   superl61  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示