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. SMU Summer 2023 Contest Round 2

    SMU Summer 2023 Contest Round 2 A. Treasure Hunt 当\(x1 - x2\)的差值与\(y1-y2\)的差值都能被\(x,y\)整除时,且商之和为2的倍数 ...

  2. ZPL Viewer工具网站

    新上线的ZPL Viewer工具网站 大家好! 在工作中,我们经常需要设计和预览ZPL(Zebra Programming Language)文件.以前,我一直使用ZPL Design这类工具,但后来 ...

  3. CVSS(Common Vulnerability Scoring System)打分规则解读

    CVSS(Common Vulnerability Scoring System)提供了一种根据漏洞的主要特征进行打分,反映其严重性的方法.CVSS 已成为被广泛使用的标准. 下面是CVSS 3.1版 ...

  4. 解决 Rust WebAssembly 启动 Web 程序报错

    当你艰难入门 Rust ,并满怀斗志准备投身 WebAssembly,第一课也许会先给你泼盆凉水. 跟随 <Rust 和 WebAssembly> 文档的指引,一路 install.cod ...

  5. IE中在线预览PDF文件

    今天在项目中偶然遇到一个需要在线查看pdf的需求.在查阅一些资料之后使用了最简单的写法(需要在客户端安装AdbeRdr11000_zh_CN_11.0.0.379.exe软件). 还有其他方法可以实现 ...

  6. 我恨 gevent

    报错了一晚上,最后发现是 python 版本不对.3.11,3.12,3.8,3.10 试了个遍,最后 3.10 终于编译通过了‍ 还有这个 greenlet,每次都是它和 gevent 合着来恶心我 ...

  7. jenkins动态切换环境

    一.代码层实现动态切换 1.首先在conftest.py下声明pytest_addoption钩子函数,写法如下 def pytest_addoption(parser): # 设置要接收的命令行参数 ...

  8. 搜索组件优化 - Command ⌘K

    前言: DevNow 项目中我们使用了 DocSearch 来实现搜索功能,但是由于有以下的限制: 您的网站必须是技术文档或技术博客. 您必须是网站的所有者,或者至少具有更新其内容的权限 您的网站必须 ...

  9. 有哪些让你「 爽到爆炸 」的 Windows 软件?

    前言 本文源于知乎的一个提问,如标题所示:有哪些让你「 爽到爆炸 」的 Windows 软件?今天大姚给大家分享6款C#/.NET开源且免费的Windows软件,希望可以帮助大家提高学习.开发.办公效 ...

  10. C# HttpClient 基本使用方式(一)

    .NetCore主要提供了HttpWebRequest,WebClient,HttpClient这三种访问web的方式,其中HttpWebRequest,WebClient都在官方被标注为已过时,如果 ...