专栏文章

P9871 天天爱打卡

P9871题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miqyk5ul
此快照首次捕获于
2025/12/04 12:51
3 个月前
此快照最后确认于
2025/12/04 12:51
3 个月前
查看原文

暴力转移

定义 fif_i 表示第 ii 天强制跑步,前 ii 天最多能拿到的能量。
枚举本次跑步的起点 jj,则 [j,i][j,i] 跑步,获得的能量是 [l,r][j,i][l,r]\in[j,i] 的挑战。
上一次跑步的起点可以是 [1,j2][1,j-2] 的任意一个点,也可以是 00 表示一开始不跑步。
所以再设一个 g[i]=maxj=1i1fjg[i]=\max_{j=1}^{i-1}{f_j},则
f[i]=maxj=ik+1ig[j2](ij+1)×d+valf[i]=\max_{j=i-k+1}^{i}g[j-2]-(i-j+1)\times d+\sum val
CPP
//28pts 暴力
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+7;
int idx,T,n,m,k,d,dp[N],f[N];
struct NODE{
	int len,val;
};
vector<NODE> a[N];
signed main(){
//	freopen("run.in","r",stdin);
//	freopen("run.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>idx>>T;
	while(T--){
		cin>>n>>m>>k>>d;
		memset(dp,0,sizeof dp);
		memset(f,0,sizeof f);
		for(int i=1;i<=n;i++) a[i].clear();
		for(int i=1;i<=m;i++){
			int x,y,z;
			cin>>x>>y>>z;
			NODE res={y,z};
			a[x].push_back(res);
		}
		for(int i=1;i<=n;i++){
			for(int j=i;j>=max(0ll,i-k+1ll);j--){
				int w=0;
				for(int x=j;x<=i;x++){
					for(auto y:a[x]){
						if(y.len<=(x-j+1)){
							w+=y.val;
						}
					}
				}
				dp[i]=max(dp[i],f[j-2]-(i-j+1)*d+w);
			}
			f[i]=max(f[i-1],dp[i]);
		}
		cout<<f[n]<<'\n';
	}
	return 0;
}

正解

用线段树下标表示决策点,对应的值表示决策值,这样就可以直接:f[i]=query(1,ik+1,i)f[i]=query(1,i-k+1,i) 了。
观察如何 ii+1i\to i+1 对当前决策点的影响:
  1. 首先 [0,i1][0,i-1] 区间全部减去 dd 的贡献。
  2. 一个 [l,r][l,r]r=ir=i 的挑战在此刻插入线段树中。因为线段树维护的是跑步起点 jj。所以对于所有在 [0,l][0,l] 的点,其实开始一直跑的话都是会得到贡献的。所以插入。
  3. g[j2]g[j-2],发现 gg 的值会作用在两个位置之后。所以插入到两个位置后即可。
然后进行离散化。但实现细节有点恶心……只能看代码理解咯?
CPP
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+7;
int idx,T,n,m,k,d,f[N];
struct NODE{
	int l,r,val;
}a[N];
bool cmp(NODE a1,NODE a2){
	return a1.r<a2.r;
}
struct TREE{
	int l,r,mx,tag;
}tr[N<<2];
void push_up(int p){
	tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx);
}
void build(int p,int l,int r){
	tr[p]={l,r,0,0};
	if(l==r) return;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	push_up(p);
}
void push_down(int p){
	if(tr[p].tag==0) return;
	tr[p<<1].tag+=tr[p].tag, tr[p<<1].mx+=tr[p].tag;
	tr[p<<1|1].tag+=tr[p].tag, tr[p<<1|1].mx+=tr[p].tag;
	tr[p].tag=0;
}
void update(int p,int l,int r,int x){
	if(l<=tr[p].l&&tr[p].r<=r){
		tr[p].mx+=x, tr[p].tag+=x;
		return;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	push_down(p);
	if(l<=mid) update(p<<1,l,r,x);
	if(mid<r) update(p<<1|1,l,r,x);
	push_up(p);
}
int query(int p,int l,int r){
	if(l>r) return 0; 
	if(l<=tr[p].l && tr[p].r<=r){
		return tr[p].mx;
	}
	int mid=(tr[p].l+tr[p].r)>>1,res=-114514;
	push_down(p);
	if(l<=mid) res=max(res,query(p<<1,l,r));
	if(mid<r) res=max(res,query(p<<1|1,l,r));
	return res;
}
int t[N],cnt;
int getid(int x){
	return lower_bound(t+1,t+1+cnt,x)-t;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>idx>>T;
	while(T--){
		cin>>n>>m>>k>>d; cnt=0;
		memset(f,0,sizeof f);
		for(int i=1;i<=m;i++){
			int x,y,z;
			cin>>x>>y>>z;
			a[i]={x-y,x,z};
			t[++cnt]=x-y;
			t[++cnt]=x;
		}
		sort(t+1,t+1+cnt);
		cnt=unique(t+1,t+1+cnt)-t-1;
		build(1,0,cnt+10);
		for(int i=1;i<=m;i++){
			a[i].l=getid(a[i].l);
			a[i].r=getid(a[i].r);
		}
		sort(a+1,a+1+m,cmp);
		int res=0,p=1;
		//tr[x] 查找到的是本次跑步的起点的前一个点 
		for(int i=1;i<=cnt;i++){//第 i 个关键位置 
			update(1,0,i-1,-d*(t[i]-t[i-1]));
			while(p<=m && a[p].r==i){
				update(1,0,a[p].l, a[p].val);
				p++;
			}
			res=max(res,query(1,getid(t[i]-k),i-1));
			update(1,i+1,i+1,res);
		}
		cout<<res<<'\n';
	}
	return 0;
}


评论

0 条评论,欢迎与作者交流。

正在加载评论...