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. 用GDI+旋转多边形来绘制一个时钟摸拟小程序

    效果图 在头文件类中声明变量 TCHAR m_dayStr[4]; // 日期 TCHAR m_weekStr[4]; // 星期 Gdiplus::Font* m_pFont; // 字体 Gdip ...

  2. 快手 内推码:TYORVzmsw 秋招 应届生/实习生 真正本人内推 已有多人在我内推之后,接连顺利通过了HR筛选、用人部门筛选、面试!

    内推码:TYORVzmsw 校园招聘岗位列表:https://campus.kuaishou.cn/#/campus/jobs?code=TYORVzmsw 真正的本人内部推荐! 已有多人在我内推之后 ...

  3. 操作 JAR 文件

    列出 JAR 文件内容 使用 jar 命令来列出 JAR 文件的内容: jar tf myapp.jar -t 选项表示列出文件,-f 表示指定 JAR 文件. 解压 JAR 文件 使用 jar 命令 ...

  4. draw.io 输入数学公式

    首先我们要把数学排版功能打开: 然后输入数学公式: AsciiMath 公式由 ` 包裹,如:`a2+b2 = c^2` LaTeX 公式由 $$ 包裹,如:$$\sqrt{3×-1}+(1+x)^2 ...

  5. Ubuntu 更换 macOS Big Sur 主题

    我们很多人使用 Mac 的原因之一是 macOS 是最像 Linux 的操作系统(bushi),而 macOS 精美的图形界面又让我们欲罢不能.那么能不能将 macOS 的图形界面搬到 Linux 上 ...

  6. Docker 发布镜像

    发布镜像 在 Docker Hub 发布镜像 登陆到 Docker Hub docker login 标记镜像并推送到 Docker Hub docker tag <image>:< ...

  7. 谈谈 Nginx 那点事【二】

    前言 在上一讲 谈谈 Nginx 那点事[一] 中,介绍了Nginx的安装及基本结构,今天将工作中Nginx的一些配置,及常用的场景做一些总结. 这一讲总结的内容主要是关于Nginx服务配置.静态资源 ...

  8. netcore高级知识点,内存对齐,原理与示例

    最近几年一直从事物联网开发,与硬件打交道越来越多,发现越接近底层开发对性能的追求越高,毕竟硬件资源相对上层应用来实在是太缺乏了.今天想和大家一起分享关于C#中的内存对齐,希望通过理解和优化内存对齐,可 ...

  9. 小tips:npm与npx的区别

    npm npm是Node.js的软件包管理器,其目标是自动化的依赖性和软件包管理. 这意味着,可以在package.json文件中为项目指定所有依赖项(软件包),当需要为其安装依赖项时,只要运行npm ...

  10. CSS – border-radius (Rounded Corners)

    前言 之前的文章 CSS – W3Schools 学习笔记 (3), 这篇独立出来写, 作为整理. 参考: Youtube – Advanced CSS Border-Radius Tutorial ...