题解:NOIP2023 天天爱打卡
算法一
upd :2024/10/31。
记
考虑如何从
直接做是
考虑优化掉枚举区间的
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;
}
对于固定的
下面讲一下代码细节,主要是离散化之后边界条件以及各种加减
我的代码中是枚举跑步的区间
-
关于
的下界:倍增或者二分。 -
关于转移:
时, 直接从 继承答案。 -
关于加减
:想清楚谁转移到谁,贡献区间长度是 ,不是 。 -
关于特判:由于离散化后数轴上只保留了端点,所以可能存在这样一种情况:
但 ,但实际上 。那么此时 到 的转移应该是而不是
#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,
直接做是
优化1:
优化2:把每个挑战挂在它的终点上,这样每个挑战只会被访问一次。
优化3:记
这个东西可以用线段树优化,只需要维护区间加,区间求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;
}
总结
- 本题中的状态定义颇具特色,
表示不选第 天,这样就可以表示 某一段全选。 - 枚举区间的复杂度过高,要想到精确添加贡献的方法。
- 线段树优化dp一定先写朴素dp再优化,否则很多细节根本注意不到!
- 最后就是离散化之后修改和查询有一些细节很需要注意!
(超级难调)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具