题解:[NOIP2023] 天天爱打卡
记 \([l,r,w]\) 表示 \(l\sim r\) 每天都跑步,小 T 请用户吃饭造成的提高为 \(w\)。
测试点 \(1\sim9\)
一个非常显然的 \(\mathcal O(nk)\) 的 DP。
设 \(\textit{dp}_{i,j}\) 为在第 \(i\) 天结束时连续打卡了 \(j\) 天的最大能量值。
则有:
答案即 \(\max\limits_{i=0}^k\textit{dp}_{n,i}\)。
期望得分:\(\text{36pts}\)。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e3,K=N,M=1e5;
vector<pair<int,int> >a[N+1];
int n,m,k,d;
ll dp[N+1][K+1];
void Start(){
for(int i=1;i<=n;i++){
a[i].resize(0);
}
memset(dp,0,sizeof(dp));
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
cin>>c>>T;
while(T--){
Start();
cin>>n>>m>>k>>d;
for(int i=1;i<=m;i++){
int x,y,v;
cin>>x>>y>>v;
a[x].push_back({y,v});
}
for(int i=1;i<=n;i++){
dp[i][0]=dp[i-1][0];
for(int j=1;j<=k;j++){
dp[i][0]=max(dp[i][0],dp[i-1][j]);
}
for(int j=1;j<=k;j++){
dp[i][j]=dp[i-1][j-1]-d;
for(auto pl:a[i]){
int &y=pl.first,&v=pl.second;
if(j>=y){
dp[i][j]+=v;
}
}
}
}
ll ans=dp[n][0];
for(int j=1;j<=k;j++){
ans=max(ans,dp[n][j]);
}
cout<<ans<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
考虑优化上面的 DP,所以更改 DP 状态,因为 \(\mathcal O(1)\) 转移已经不能继续优化。
测试点 \(1\sim7\)
设 \(\textit{dp}_{i,1},\textit{dp}_{i,0}\) 分别为第 \(i\) 天是否跑步的最大能量值。
则有:
其中,\(\operatorname{calc}(l,r)\) 表示 \(l\sim r\) 一直跑步增加的能量值,显然可以 \(\mathcal O(m)\) 计算。
于是得到了一个更劣的 \(\mathcal O(nmk)\) 的解法。将给定的 \([l_i,r_i]\) 按照 \(r_i\) 排序后单次计算 \(\operatorname{calc}(l,r)\) 时在给定区间上二分,可以减小常数。期望复杂度为 \(\mathcal O\left(nk\left(\log m+\dfrac mn\right)\right)=\mathcal O\left(nk\log m+mk\right)\)。
期望得分 \(\text{28pts}\)。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e5,K=N,M=1e5;
struct line{
int l,r,w;
}a[M+1];
int n,m,k,d;
ll dp[N+1][2];
ll calc(int l,int r){
ll ans=0;
int L=0,R=m;
while(L<R){
int mid=L+R+1>>1;
if(a[mid].r<=r){
L=mid;
}else{
R=mid-1;
}
}
for(int i=L;1<=i&&i<=m&&l<=a[i].r;i--){
if(l<=a[i].l){
ans+=a[i].w;
}
}
return ans;
}
void Start(){
memset(dp,0,sizeof(dp));
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
cin>>c>>T;
while(T--){
Start();
cin>>n>>m>>k>>d;
for(int i=1;i<=m;i++){
int &l=a[i].l,&r=a[i].r,&w=a[i].w;
int x,y;
cin>>x>>y>>w;
r=x;
l=x-y+1;
}
sort(a+1,a+m+1,[](line a,line b){
return a.r<b.r;
});
// for(int i=1;i<=m;i++){
// cerr<<"["<<a[i].l<<","<<a[i].r<<"]:"<<a[i].w<<endl;
// }
for(int i=1;i<=n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
dp[i][1]=dp[i-1][0]-d+calc(i,i);
for(int j=2;j<=i&&j<=k;j++){
dp[i][1]=max(dp[i][1],dp[i-j][0]-1ll*j*d+calc(i-j+1,i));
}
}
cout<<max(dp[n][0],dp[n][1])<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
测试点 \(8\sim11\)
考虑优化上面的 DP。
发现 \(\operatorname{calc}(l,r)\) 具有的性质就是 \(r\) 单调递增。因此可以想到数据结构维护 \(l_i\) 处增加的 \(w_i\)。
在 DP 之前将 \([l_i,r_i]\) 按 \(r_i\) 排序,DP 时依次加入即可。设 \(\operatorname{query}(x)\) 表示 \(l_i\geq x\) 的 \(w_i\) 之和。
则有:
时间复杂度:\(\mathcal O((nk+m)\log m)\)。期望得分:\(\text{36pts}\)。原地打转。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=1e5,K=N,M=1e5;
struct BIT{
ll t[N+1];
int tag[N+1],Tag;
void clear(){
Tag++;
}
int lowbit(int x){
return x&-x;
}
ll query(int x){
ll ans=0;
while(x<=N){
if(tag[x]!=Tag){
t[x]=0;
tag[x]=Tag;
}
ans+=t[x];
x+=lowbit(x);
}
return ans;
}
void add(int x,int k){
while(x){
if(tag[x]!=Tag){
t[x]=0;
tag[x]=Tag;
}
t[x]+=k;
x-=lowbit(x);
}
}
}t;
struct line{
int l,r,w;
}a[M+1];
int n,m,k,d;
ll dp[N+1][2];
void Start(){
t.clear();
memset(dp,0,sizeof(dp));
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
cin>>c>>T;
while(T--){
Start();
cin>>n>>m>>k>>d;
for(int i=1;i<=m;i++){
int &l=a[i].l,&r=a[i].r,&w=a[i].w;
int x,y;
cin>>x>>y>>w;
r=x;
l=x-y+1;
}
sort(a+1,a+m+1,[](line a,line b){
if(a.r!=b.r){
return a.r<b.r;
}else{
return a.l<b.l;
}
});
for(int i=1,p=1;i<=n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
while(p<=m&&a[p].r<=i){
t.add(a[p].l,a[p].w);
p++;
}
dp[i][1]=-1ll<<60;
for(int j=max(i-k,0);j<i;j++){
dp[i][1]=max(dp[i][1],dp[j][0] - 1ll*(i-j)*d + t.query(j+1));
}
}
cout<<max(dp[n][0],dp[n][1])<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
测试点 \(10\sim14\)
发现 \(\textit{dp}_{i,1}\) 的递推式其实是一个区间递推,因此可以考虑线段树优化 DP。
线段树维护 \(\textit{dp}_{j,0}\) 对于 DP 的贡献,即 \(\textit{dp}_{j,0}-(i-j)d+\operatorname{query}(j+1)\)。
- \(-(i-j)d\) 在 \(i\) 变化时可以通过区间加法维护,在 \([0,i-1]\) 上减去 \(d\) 即可。
- \(\operatorname{query}(j+1)\) 在 \(i\) 变化时维护对应增加的 \([l,r,w]\),在线段树上的 \([0,l-1]\) 这段区间进行区间加法加 \(w\) 即可。
- 查询只需要线段树维护区间 \([\max(i-k,0),i-1]\) 最大值即可。
时间复杂度:\(\mathcal O(n\log n+m\log m)\)。期望得分:\(\text{56pts}\)。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const ll inf=0x3f3f3f3f3f3f3f3f;
constexpr const int N=1e5,K=N,M=1e5;
struct line{
int l,r,w;
}a[M+1];
struct segTree{
struct node{
int l,r;
ll max,tag;
}t[N<<2|1];
void up(int p){
t[p].max=max(t[p<<1].max,t[p<<1|1].max);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
t[p].tag=0;
if(l==r){
t[p].max=0;
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
up(p);
}
void down(int p){
if(t[p].tag){
t[p<<1].max+=t[p].tag;
t[p<<1].tag+=t[p].tag;
t[p<<1|1].max+=t[p].tag;
t[p<<1|1].tag+=t[p].tag;
t[p].tag=0;
}
}
void add(int p,int l,int r,ll k){
if(l<=t[p].l&&t[p].r<=r){
t[p].tag+=k;
t[p].max+=k;
return;
}
down(p);
if(l<=t[p<<1].r){
add(p<<1,l,r,k);
}
if(t[p<<1|1].l<=r){
add(p<<1|1,l,r,k);
}
up(p);
}
ll query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].max;
}
down(p);
ll ans=-inf;
if(l<=t[p<<1].r){
ans=query(p<<1,l,r);
}
if(t[p<<1|1].l<=r){
ans=max(ans,query(p<<1|1,l,r));
}
return ans;
}
}t;
int n,m,k,d;
ll dp[N+1][2];
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
cin>>c>>T;
while(T--){
cin>>n>>m>>k>>d;
t.build(1,0,n);
for(int i=1;i<=m;i++){
int &l=a[i].l,&r=a[i].r,&w=a[i].w;
int x,y;
cin>>x>>y>>w;
r=x;
l=x-y+1;
}
sort(a+1,a+m+1,[](line a,line b){
if(a.r!=b.r){
return a.r<b.r;
}else{
return a.l<b.l;
}
});
for(int i=1,p=1;i<=n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
t.add(1,i-1,i-1,dp[i-1][0]);
while(p<=m&&a[p].r<=i){
t.add(1,0,a[p].l-1,a[p].w);
p++;
}
t.add(1,0,i-1,-d);
dp[i][1]=t.query(1,max(i-k,0),i-1);
}
cout<<max(dp[n][0],dp[n][1])<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
测试点 \(15\sim25\)
考虑优化。
拆式子,得到:
这样,就避免了 \(i\) 对 \(j\) 的贡献的影响,\(j\) 的贡献即 \(\textit{dp}_{j,0}+jd+\operatorname{query}(j+1)\)。这是确定的,并且可以发现有效的决策点一定是 \(l-1\) 和 \(r\)。那么离散化 \(l-1,r\),在 \(l-1,r\) 上决策即可。
操作可以在动态开点权值线段树上维护,但是被卡常了。Loj上有 \(\text{84pts}\),洛谷上只有 \(\text{60pts}\)。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
namespace io {
const int SIZE = (1 << 25) + 1;
char ibuf[SIZE], *iS, *iT;
char obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[64];
int f, qr;
#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
inline void flush () {
fwrite (obuf, 1, oS - obuf, stdout);
oS = obuf;
}
inline void putc (char x) {
*oS ++ = x;
if (oS == oT) flush ();
}
template <class I> inline void IN (I &x) {
for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15);
x = f == -1 ? -x : x;
}
template <class I> inline void print (I x) {
if (! x) putc ('0'); if (x < 0) putc ('-'), x = -x;
while (x) qu[ ++ qr] = x % 10 + '0', x /= 10;
while (qr) putc (qu[qr -- ]);
}
struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io :: IN;
using io :: putc;
using io :: print;
typedef long long ll;
constexpr const ll inf=0x3f3f3f3f3f3f3f3f;
constexpr const int N=1e9,K=N,M=1e5;
struct line{
int l,r,w;
}a[M+1];
struct segTree{
struct node{
int l,r;
ll max,tag;
int lChild,rChild;
}t[M*(__lg(N+1)+1)*3];
int size;
inline void clear(){
size=0;
}
inline int create(int l,int r){
size++;
t[size]={l,r};
return size;
}
inline void up(int p){
t[p].max=max(t[t[p].lChild].max,t[t[p].rChild].max);
}
inline void down(int p){
if(!t[p].lChild){
t[p].lChild=create(t[p].l,t[p].l+t[p].r>>1);
}
t[t[p].lChild].max+=t[p].tag;
t[t[p].lChild].tag+=t[p].tag;
if(!t[p].rChild){
t[p].rChild=create((t[p].l+t[p].r>>1)+1,t[p].r);
}
t[t[p].rChild].max+=t[p].tag;
t[t[p].rChild].tag+=t[p].tag;
t[p].tag=0;
}
void add(int p,int l,int r,ll k){
if(l<=t[p].l&&t[p].r<=r){
t[p].tag+=k;
t[p].max+=k;
return;
}
down(p);
if(l<=t[t[p].lChild].r){
add(t[p].lChild,l,r,k);
}
if(t[t[p].rChild].l<=r){
add(t[p].rChild,l,r,k);
}
up(p);
}
ll query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].max;
}
down(p);
ll ans=-inf;
if(l<=t[t[p].lChild].r){
ans=query(t[p].lChild,l,r);
}
if(t[t[p].rChild].l<=r){
ans=max(ans,query(t[p].rChild,l,r));
}
return ans;
}
}t;
int n,m,k,d;
ll dp[M*3+1][2];
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
IN(c);IN(T);
// cin>>c>>T;
while(T--){
IN(n);IN(m);IN(k);IN(d);
// cin>>n>>m>>k>>d;
t.clear();
t.create(0,n);
static int tmp[M*3+1];
int len=0;
for(int i=1;i<=m;i++){
int &l=a[i].l,&r=a[i].r,&w=a[i].w;
int x,y;
IN(x);IN(y);IN(w);
// cin>>x>>y>>w;
r=x;
l=x-y+1;
if(l-1){
tmp[++len]=l-1;
}
tmp[++len]=r;
}
sort(tmp+1,tmp+len+1);
len=unique(tmp+1,tmp+len+1)-tmp-1;
sort(a+1,a+m+1,[](line a,line b){
if(a.r!=b.r){
return a.r<b.r;
}else{
return a.l<b.l;
}
});
for(int i=1,p=1;i<=len;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
t.add(1,tmp[i-1],tmp[i-1],dp[i-1][0] + 1ll*tmp[i-1]*d);
while(p<=m&&a[p].r<=tmp[i]){
t.add(1,0,a[p].l-1,a[p].w);
p++;
}
dp[i][1]=t.query(1,max(tmp[i]-k,0),tmp[i]-1)-1ll*tmp[i]*d;
// cerr<<"dp["<<setw(5)<<i<<"][0]="<<setw(12)<<dp[i][0]<<" "<<"dp["<<setw(5)<<i<<"][1]="<<setw(12)<<dp[i][1];
// cerr<<" r="<<setw(8)<<a[p-1].r<<endl;
}
print(max(dp[len][0],dp[len][1]));
putc('\n');
// cout<<max(dp[len][0],dp[len][1])<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
于是可以写普通线段树维护,时间复杂度 \(\mathcal O(m\log m)\)。
AC 代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const ll inf=0x3f3f3f3f3f3f3f3f;
constexpr const int N=1e9,K=N,M=1e5;
struct line{
int l,r,w;
}a[M+1];
struct segTree{
struct node{
int l,r;
ll max,tag;
}t[M<<4|1];
inline void up(int p){
t[p].max=max(t[p<<1].max,t[p<<1|1].max);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
t[p].tag=0;
if(l==r){
t[p].max=0;
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
up(p);
}
inline void down(int p){
if(t[p].tag){
t[p<<1].max+=t[p].tag;
t[p<<1].tag+=t[p].tag;
t[p<<1|1].max+=t[p].tag;
t[p<<1|1].tag+=t[p].tag;
t[p].tag=0;
}
}
void add(int p,int l,int r,ll k){
if(l<=t[p].l&&t[p].r<=r){
t[p].tag+=k;
t[p].max+=k;
return;
}
down(p);
if(l<=t[p<<1].r){
add(p<<1,l,r,k);
}
if(t[p<<1|1].l<=r){
add(p<<1|1,l,r,k);
}
up(p);
}
ll query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].max;
}
down(p);
ll ans=-inf;
if(l<=t[p<<1].r){
ans=query(p<<1,l,r);
}
if(t[p<<1|1].l<=r){
ans=max(ans,query(p<<1|1,l,r));
}
return ans;
}
}t;
int n,m,k,d;
ll dp[M<<1|1][2];
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int c,T;
cin>>c>>T;
while(T--){
cin>>n>>m>>k>>d;
t.build(1,0,m<<1);
static int tmp[M<<1|1];
int len=0;
for(int i=1;i<=m;i++){
int &l=a[i].l,&r=a[i].r,&w=a[i].w;
int x,y;
cin>>x>>y>>w;
r=x;
l=x-y/*+1*/;
tmp[++len]=l;
tmp[++len]=r;
}
sort(tmp+1,tmp+len+1);
len=unique(tmp+1,tmp+len+1)-tmp-1;
for(int i=1;i<=m;i++){
a[i].l=lower_bound(tmp+1,tmp+len+1,a[i].l)-tmp;
a[i].r=lower_bound(tmp+1,tmp+len+1,a[i].r)-tmp;
}
sort(a+1,a+m+1,[](line a,line b){
if(a.r!=b.r){
return a.r<b.r;
}else{
return a.l<b.l;
}
});
for(int i=1,p=1,last=0;i<=len;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
t.add(1,i-1,i-1,dp[i-1][0] + 1ll*tmp[i-1]*d);
while(p<=m&&a[p].r<=i){
t.add(1,0,a[p].l,a[p].w);
p++;
}
while(tmp[last]<tmp[i]-k){
last++;
}
dp[i][1]=t.query(1,last,i-1)-1ll*tmp[i]*d;
}
cout<<max(dp[len][0],dp[len][1])<<'\n';
}
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}