NOIP2023 天天爱打卡 题解

题意描述

小 T 同学非常热衷于跑步。为了让跑步更加有趣,他决定制作一款叫做《天天爱打卡》的软件,使得用户每天都可以进行跑步打卡。

开发完成后,小 T 同学计划进行试运行,他找了大 Y 同学来帮忙。试运行共 n n n 天,编号为从 1 1 1 n n n

对大 Y 同学来说,如果某天他选择跑步打卡,那么他的能量值会减少 d d d。初始时,他的能量值是 0 0 0,并且试运行期间他的能量值可以是负数

而且大 Y 不会连续跑步打卡超过 k k k 天;即不能存在 1 ≤ x ≤ n − k 1\le x\le n-k 1xnk,使得他在第 x x x 到第 x + k x+k x+k 天均进行了跑步打卡。

小 T 同学在软件中设计了 m m m 个挑战,第 i i i 1 ≤ i ≤ m 1\le i \le m 1im)个挑战可以用三个正整数 ( x i , y i , v i ) (x_i,y_i,v_i) (xi,yi,vi) 描述,表示如果在第 x i x_i xi 天时,用户已经连续跑步打卡至少 y i y_i yi 天(即第 x i − y i + 1 x_i-y_i+1 xiyi+1 到第 x i x_i xi 天均完成了跑步打卡),那么小 T 同学就会请用户吃饭,从而使用户的能量值提高 v i v_i vi

现在大 Y 想知道,在软件试运行的 n n n 天结束后,他的能量值最高可以达到多少?

题解

考场上没时间写离散化, 100 → 56 100\to56 10056,警惕看错时间仙人。

8pts做法

爆搜进行哪个挑战,判断下是否满足要求即可。
时间复杂度 O ( 2 n )   O ( n 2 n ) O(2^n)~O(n2^n) O(2n) O(n2n),预计得分8pts。

36pts做法

尝试设计一个 D P DP DP
首先先将题意中的挑战定义下形式。
对第 i i i 个挑战, ( l i , r i , v i ) (l_i,r_i,v_i) (li,ri,vi) 表示从第 l i = x i − y i + 1 l_i=x_i-y_i+1 li=xiyi+1 天开始,到第 r i = x i r_i=x_i ri=xi 天为止连续跑步,能够得到的能量值为 v i v_i vi

f i , 0 / 1 f_{i,0/1} fi,0/1 分别表示第 i i i强制不选/不强制不选的最大能量。
在附带一个辅助数组 a r r i , j = ∑ r k < j , l k = i v k arr_{i,j}=\sum_{r_k<j,l_k=i}v_k arri,j=rk<j,lk=ivk(具体实现看代码)。
不难发现有转移式
f i , 0 = max ⁡ f i − 1 , 0 , f i − 1 , 1 f_{i,0} = \max{f_{i-1,0},f_{i-1,1}} fi,0=maxfi1,0,fi1,1
f i , 1 = max ⁡ i − j < k f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k − ( i − j + 1 ) f_{i,1} = \max_{i-j<k}{f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} - (i-j+1)} fi,1=ij<kmaxfj1,0+k[j,i]arrk(ij+1)
当然,根据定义 f i , 1 ← max ⁡ f i , 1 , f i , 0 f_{i,1}\gets \max{f_{i,1},f_{i,0}} fi,1maxfi,1,fi,0

然后在实现辅助数组的时候,只需要开一维的 a r r j arr_j arrj,然后每更新一次右端点 i i i,就将满足 r k = i r_k=i rk=i 的挑战 k k k 提出来,让 a r r l k ← a r r l k + v k arr_{l_k}\gets arr_{l_k}+v_k arrlkarrlk+vk
实现并不难。

36pts赛时代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 3e5 + 10;
int n, m, k, d;
int f[maxn][2], arr[maxn];
vector<pair<int,int> > vec[maxn];
signed main(){
    //freopen("run.in","r",stdin);
    //freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
    n = read(), m = read(), k = read(), d = read();
    int x, y, v;
    for(int i = 1;i <= n;i++)vec[i].clear();
    memset(f,0,sizeof(f));memset(arr,0,sizeof(arr));
    for(int i = 1;i <= m;i++){
        x = read(), y = read(),  v = read();
        vec[x].push_back(make_pair(x - y + 1,v));
    }
    int ans = 0;
    for(int i = 1;i <= n;i++){
        int sum = 0;
        for(auto j : vec[i]){arr[j.first] += j.second;}
        for(int j = i;i - j < k && j > 0;j--){
            sum += arr[j];
            f[i][1] = max(f[i][1],f[j - 1][0] + (j - 1) * d + sum - i * d);
        }
        f[i][0] = max(f[i - 1][0],f[i - 1][1]);
        ans = max(ans,max(f[i][0],f[i][1]));
    }
    printf("%lld\n",ans);
}
    return 0;
}

56pts做法

先将刚刚的柿子改一下:
f i , 1 = max ⁡ i − j < k f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k − ( i − j + 1 ) f_{i,1} = \max_{i-j<k}{f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} - (i-j+1)} fi,1=ij<kmaxfj1,0+k[j,i]arrk(ij+1)
⇔ f i , 1 = − i + max ⁡ i − j < k ( f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k + j − 1 ) \Leftrightarrow f_{i,1} =-i+ \max_{i-j<k}{(f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +j-1)} fi,1=i+ij<kmax(fj1,0+k[j,i]arrk+j1)
发现将 i i i 提出来之后,后面的东西可以类似扫描线维护。
具体而言,使用线段树维护每一个位置上的 f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k + j − 1 f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +j-1 fj1,0+k[j,i]arrk+j1
每次 i ← i + 1 i\gets i + 1 ii+1 之后,将所有满足 r k = i r_k=i rk=i 的挑战 k k k 提出来,让线段树上区间 [ 1 , l k ] [1,l_k] [1,lk] 中的每一个位置加上 v k v_k vk
解释:因为只要包含了区间 [ l k , r k ] [l_k,r_k] [lk,rk]就能拿到 v k v_k vk 的贡献,而现在和将来的右端点 r ≥ r k r\ge r_k rrk,所以只要 l ≤ l k l\le l_k llk 的位置都会包含这个区间,所以区间加。
然后 f i , 1 ← max ⁡ [ i − k + 1 , i ] f_{i,1}\gets\max[i-k+1,i] fi,1max[ik+1,i]
这两个操作都是线段树基本操作。
时间复杂度和空间复杂度都是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

56pts赛时代码丑极了

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 3e5 + 10;
int n, m, k, d;
int f[maxn][2], arr[maxn];
vector<pair<int,int> > vec[maxn];
struct Segment_Tree{
    struct node{
        int maxx, tag;
        node(int maxx = 0,int tag = 0):maxx(maxx),tag(tag){}
    }d[maxn << 2];
    node mergenode(node l,node r){
        return node(max(l.maxx,r.maxx));
    }
    void pushdown(int p){
        if(d[p].tag){
            d[p << 1].tag += d[p].tag;
            d[p << 1].maxx += d[p].tag;
            d[p << 1 | 1].tag += d[p].tag;
            d[p << 1 | 1].maxx += d[p].tag;
            d[p].tag = 0;
        }
    }
    void build(int l,int r,int p){
        if(l == r){d[p] = node();return;}
        int mid = l + r >> 1;
        build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    void update(int l,int r,int pos,int p,int add){
        if(l == r && l == pos){d[p].maxx += add;return;}
        int mid = l + r >> 1;pushdown(p);
        if(pos <= mid)update(l,mid,pos,p << 1,add);
        else update(mid + 1,r,pos,p << 1 | 1,add);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    void update(int l,int r,int s,int t,int p,int add){
        if(s <= l && r <= t){d[p].tag += add;d[p].maxx += add;return;}
        int mid = l + r >> 1; pushdown(p);
        if(s <= mid)update(l,mid,s,t,p << 1,add);
        if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,add);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    node query(int l,int r,int s,int t,int p){
        if(s <= l && r <= t){return d[p];}
        int mid = l + r >> 1;pushdown(p);
        if(t <= mid)return query(l,mid,s,t,p << 1);
        if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
        return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
    }
    int query(int l,int r){return query(0,n + 1,l,r,1).maxx;}
    // void update(int pos,int add){update(0,n + 1,pos,1,add);}
    void update(int l,int r,int add){update(0,n + 1,l,r,1,add);}
}tree;
signed main(){
    //freopen("run.in","r",stdin);
    //freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
    n = read(), m = read(), k = read(), d = read();
    int x, y, v;
    for(int i = 1;i <= n;i++)vec[i].clear();
    memset(f,0,sizeof(f));memset(arr,0,sizeof(arr));
    tree.build(0,n + 1,1);
    for(int i = 1;i <= m;i++){
        x = read(), y = read(),  v = read();
        vec[x].push_back(make_pair(x - y + 1,v));
    }
    int ans = 0;
    
    bool switchcase = (n <= 1e3);
// switchcase = false;

    if(switchcase){
        for(int i = 1;i <= n;i++){
            int sum = 0;
            for(auto j : vec[i]){arr[j.first] += j.second;}
            for(int j = i;i - j < k && j > 0;j--){
                sum += arr[j];
                f[i][1] = max(f[i][1],f[j - 1][0] + (j - 1) * d + sum - i * d);
            }
            f[i][0] = max(f[i - 1][0],f[i - 1][1]);
            ans = max(ans,max(f[i][0],f[i][1]));
        }
        printf("%lld\n",ans);
    }
    else{
        for(int i = 1;i <= n;i++){
            for(auto j : vec[i]){tree.update(1,j.first,j.second);}
            
            f[i][1] = tree.query(max(1LL,i - k + 1),i) - i * d;
            f[i][0] = max(f[i - 1][0],f[i - 1][1]);
            ans = max(ans,max(f[i][0],f[i][1]));
            tree.update(i + 1,i + 1,f[i][0] + i * d);
        }
        printf("%lld\n",ans);

    }
}
    return 0;
}

100pts做法

考虑有效的位置有多少个。
如果没思路不妨回头看看DP式子中有几个有效位置。
很显然,对于每个挑战 i i i,只有 l i − 1 , l i , r i l_i-1,l_i,r_i li1,li,ri 这三个位置是有效的。
于是乎离散化,设 x i x_i xi 表示编号为 i i i 的真实位置。
那么DP式子也有点变化(变化不大):
f i , 1 = − x i + max ⁡ i − j < k ( f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k + x j − 1 ) f_{i,1} =-x_i+ \max_{i-j<k}{(f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +x_j-1)} fi,1=xi+ij<kmax(fj1,0+k[j,i]arrk+xj1)
同样线段树上每一个位置存放数字也有点变化。
对于位置 j j j 存储: f j − 1 , 0 + ∑ k ∈ [ j , i ] a r r k + x j − 1 f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +x_j-1 fj1,0+k[j,i]arrk+xj1
然后就能从 56 → 100 56\to100 56100考场上想出来了但是没时间了(悲。
时间复杂度和空间复杂度都是 O ( 3 m log ⁡ 3 m ) O(3m\log 3m) O(3mlog3m)

100pts代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
inline int read(){
    int x = 0, f =1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
bool st;
const int maxn = 9e5 + 10;
struct Segment_Tree{
    int n;
    struct node{
        ll maxx, tag;
        node(ll maxx = 0,ll tag = 0):maxx(maxx),tag(tag){}
    }d[maxn << 2];
    node mergenode(node l,node r){
        return node(max(l.maxx,r.maxx));
    }
    void pushdown(int p){
        if(d[p].tag){
            d[p << 1].tag += d[p].tag;
            d[p << 1].maxx += d[p].tag;
            d[p << 1 | 1].tag += d[p].tag;
            d[p << 1 | 1].maxx += d[p].tag;
            d[p].tag = 0;
        }
    }
    void build(int l,int r,int p){
        if(l == r){d[p] = node();return;}
        int mid = l + r >> 1;
        build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    // void update(int l,int r,int pos,int p,int add){
    //     if(l == r && l == pos){d[p].maxx += add;return;}
    //     int mid = l + r >> 1;pushdown(p);
    //     if(pos <= mid)update(l,mid,pos,p << 1,add);
    //     else update(mid + 1,r,pos,p << 1 | 1,add);
    //     d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    // }
    void update(int l,int r,int s,int t,int p,ll add){
        if(s <= l && r <= t){d[p].tag += add;d[p].maxx += add;return;}
        int mid = l + r >> 1; pushdown(p);
        if(s <= mid)update(l,mid,s,t,p << 1,add);
        if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,add);
        d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
    }
    node query(int l,int r,int s,int t,int p){
        if(s <= l && r <= t){return d[p];}
        int mid = l + r >> 1;pushdown(p);
        if(t <= mid)return query(l,mid,s,t,p << 1);
        if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
        return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
    }
    void build(int x){n = x;build(0,n + 1,1);}
    int query(int l,int r){return query(0,n + 1,l,r,1).maxx;}
    // void update(int pos,int add){update(0,n + 1,pos,1,add);}
    void update(int l,int r,ll add){update(0,n + 1,l,r,1,add);}
}tree;

int n, m, k;
ll d, f[maxn][2];
vector<pair<int,int> > vec[maxn];

vector<pair<pair<int,int> , int> > tmp;
vector<ll> disc;
int Lk[maxn];
bool ed;
signed main(){
    // cerr << "cost : " << (&st - &ed) / 1024 / 1024 << "Mib" << endl;
    // freopen("run.in","r",stdin);
    // freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
    n = read(), m = read(), k = read(), d = read();
    int x, y, v;
    
    disc.clear();tmp.clear();
    memset(f,0,sizeof(f));

    for(int i = 1;i <= m;i++){
        x = read(), y = read(),  v = read();
        tmp.push_back(make_pair(make_pair(x - y + 1,x),v));
        disc.push_back(x - y + 1);disc.push_back(x);disc.push_back(x - y);
    }
    disc.push_back(-1);sort(disc.begin(),disc.end());
    disc.erase(unique(disc.begin(),disc.end()),disc.end());
    int tot = disc.size() - 1;tree.build(tot);
    // cerr << "tot = " << tot << endl;
    for(int i = 1;i <= tot;i++)vec[i].clear();
// puts("12111111");

    int l = 1;
    for(int i = 0;i < m;i++){
        // printf("l = %d r = %d\n",tmp[i].first.first,tmp[i].first.second);
        tmp[i].first.first  = lower_bound(disc.begin(),disc.end(),tmp[i].first.first ) - disc.begin();
        tmp[i].first.second = lower_bound(disc.begin(),disc.end(),tmp[i].first.second) - disc.begin();
        vec[tmp[i].first.second].push_back(make_pair(tmp[i].first.first,tmp[i].second));
        // printf("l = %d r = %d\n",tmp[i].first.first,tmp[i].first.second);
    }

// puts("222222222");

    for(int i = 1;i <= tot;i++){
        while(disc[i] - disc[l] >= k && l <= i)l++;
        Lk[i] = l;
        // printf("Lk[%d] = %d, disc[%d] = %d\n",i,Lk[i],i,disc[i]);
    }
    // for(int i = 0;i < disc.size();i++){printf("disc[%d] = %d\n",i,disc[i]);}
    ll ans = 0;

// puts("3333333333");

    for(int i = 1;i <= tot;i++){
        for(auto j : vec[i]){tree.update(1,j.first,j.second);}

        f[i][1] = tree.query(Lk[i],i) - disc[i] * d;
        f[i][0] = max(f[i - 1][0],f[i - 1][1]);
        ans = max(ans,max(f[i][0],f[i][1]));
        tree.update(i + 1,i + 1,f[i][0] + disc[i] * d);
        // printf("f[%d][0] = %lld,f[%d][1] = %lld\n",i,f[i][0],i,f[i][1]);
    }
    printf("%lld\n",ans);
}
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值