NOIP2023 天天爱打卡 - luogu

算法一

upd :2024/10/31。

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

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

\[f[i] = \max_{i - j - 1 \le k} f[j] + val(j + 1, i - 1) - (i - j - 1) \times d
\]

直接做是 \(O(Tn^2m)\),能获得12pts。

考虑优化掉枚举区间的\(O(m)\)。用类似双指针的方法,可以做到每个贡献区间只加入一次,不离散化有 36pts。离散化后就是 \(O(T\min(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\), 求的是 \(\max_{i - j - 1 \le k} (f[j] + val(j + 1) + (j + 1) \times d) - i \times d\)。\(\max\) 里面的东西可以放到线段树上维护,离散化就是 \(O(Tmlogm)\)。


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

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

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

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

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

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

    \[f[j] + val(j, i)
    \]

    而不是

    \[f[j - 1] + 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]=\max_{j=i-k+1}^{i} (val(j,i) - d \times (i-j+1) + \max_{p=1}^{j-2} f[p])
\]

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

直接做是 \(O(Tn^2mk)\)。

优化1: $ \max_{p=1}^{j-2} f[p]$ 可以前缀 \(max\) 优化。

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

优化3:记 \(g[j] = val(j,i) - d \times (i-j+1) + \max_{p=1}^{j-2} f[p]\), 那么 \(f[i]=\max_{j=i-k+1}^{i} g[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再优化,否则很多细节根本注意不到!
  • 最后就是离散化之后修改和查询有一些细节很需要注意!(超级难调)

题解:NOIP2023 天天爱打卡的更多相关文章

  1. 【题解】P3129高低卡(白金)High Card Low Card

    [题解][P3129 USACO15DEC]高低卡(白金)High Card Low Card (Platinum) 考虑贪心. 枚举在第几局改变规则,在改变规则之前,尽量出比它大的最小的牌,在改变规 ...

  2. 2016ACM-ICPC Qingdao Online青岛网络赛题解

    TonyFang+Sps+我=5/12 滚了个大粗 2016年9月21日16:42:36 10题完工辣 01 题意:求形同的数中大于n的最小值 题解:预处理所有的(5194个),在这里面二分 #inc ...

  3. 「题解」:[BZOJ4558]方

    问题: 方 时间限制: 2 Sec  内存限制: 256 MB 题面 题目描述 上帝说,不要圆,要方,于是便有了这道题.由于我们应该方,而且最好能够尽量方,所以上帝派我们来找正方形 上帝把我们派到了一 ...

  4. 题解 P3451 [POI2007]ATR-Tourist Attractions

    题解 这里的做法是卡空间的做法,相比于滚动数组,这种做法因为没有三维数组寻址的大常数,所以较快. 在普通的做法中,\(dp[state][i]\) 表示以 \(i\) 结尾,那么 \(state\) ...

  5. 题解 P3831 [SHOI2012]回家的路

    什么叫分层图最短路,我不会/kk 感觉自己做法和其他题解不大一样所以过来发篇题解了. 未刻意卡常拿下最优解 题目大意 就是说给你一个 \(n \times n\) 的网格图和 \(m\) 个可换乘点, ...

  6. 76. Minimum Window Substring

    题目: Given a string S and a string T, find the minimum window in S which will contain all the charact ...

  7. 93. Restore IP Addresses

    题目: Given a string containing only digits, restore it by returning all possible valid IP address com ...

  8. WIKIOI 3243 区间翻转

    3243 区间翻转 题目描述 Description 给出N个数,要求做M次区间翻转(如1 2 3 4变成4 3 2 1),求出最后的序列 输入描述 Input Description 第一行一个数N ...

  9. hdu_5718_Oracle(大数模拟)

    题目连接:hdu_5718_Oracle 题意: 给你一串数,让你分出两个正整数,使其和最大,若不能分出来就输出"Uncertain" 题解: 当时比赛的时候还天真的去搞大数模版, ...

  10. [洛谷P4723]【模板】线性递推

    题目大意:求一个满足$k$阶齐次线性递推数列$a_i$的第$n$项. 即:$a_n=\sum\limits_{i=1}^{k}f_i \times a_{n-i}$ 题解:线性齐次递推,先见洛谷题解, ...

随机推荐

  1. 08-canvas绘制表格

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  2. .NET 7 + Vue 权限管理系统 小白快速上手

    前言 今天给大家推荐一个超实用的开源项目<.NET 7 + Vue 权限管理系统 小白快速上手>,DncZeus的愿景就是做一个.NET 领域小白也能上手的简易.通用的后台权限管理模板系统 ...

  3. 手把手教Linux驱动10-platform总线详解

    platform总线是学习linux驱动必须要掌握的一个知识点. 本文参考已发布:Linux 3.14内核 一.概念 嵌入式系统中有很多的物理总线:I2c.SPI.USB.uart.PCIE.APB. ...

  4. ios滚动列表白屏问题

    移动端分页列表,在ios上滚动加载分页时候,使用scrollTop,会引起白屏问题. 看不少文章说是使用了-webkit-overflow-scrolling: touch;引起的硬件加速问题.亲测删 ...

  5. USB入门系列(一)认识USB

    认识USB usb的类型 接头外形上 USB类型 描述 USB-A 最广泛的接口标准 USB-B 一般用于打印机.扫描仪.USBHUB等外部USB设备(j-tag就用到了) USB-C USB-C将成 ...

  6. VMware 安装 OpenWrt

    准备 OpenWrt VMDK 固件映像 你可以直接下载 VMDK 版本的 OpenWrt 固件映像,或者自己构建一个,或者从 IMG 映像文件转换.一般来说 IMG 映像文件较为容易获取,因此下面介 ...

  7. Mac m1 安装 scrcpy

    前提:已经安装 brew 1. 设定 HOMEBREW_BOTTLE_DOMAIN(不设定的时候 ,会遇到报错  Bottle missing, falling back to the default ...

  8. 熔断、限流、降级 —— SpringCloud Hystrix

    概述 Hystrix 为 微服务架构提供了一整套服务隔离.服务熔断和服务降级的解决方案.它是熔断器的一种实现,主要用于解决微服务架构的高可用及服务雪崩等问题 Hystrix 的特性如下: 服务熔断:H ...

  9. 如果nacos注册中心挂了怎么办

    当服务异常宕机,Nacos还未反应过来时,可能会发生的状况以及现有的解决方案. Nacos的健康检查 故事还要从Nacos对服务实例的健康检查说起. Nacos目前支持临时实例使用心跳上报方式维持活性 ...

  10. springboot 前端访问服务器上的图片及附件

    一.需求 后端是springboot,附件上传到服务器上,前端访问服务器上的附件,如:显示图片.视频.文件等 二.解决方法 springboot 中进行资源映射,根据路径将磁盘上的文件映射为资源返回到 ...