今天是24.8.24,离NO0IP2024也不远了,写一篇2023年的第四题,得把压箱底的绝技拿出来了
一,原题
题目描述
小 T 同学非常热衷于跑步。为了让跑步更加有趣,他决定制作一款叫做《天天爱打卡》的软件,使得用户每天都可以进行跑步打卡。
开发完成后,小 T 同学计划进行试运行,他找了大 Y 同学来帮忙。试运行共 nn 天,编号为从 11 到 nn。
对大 Y 同学来说,如果某天他选择跑步打卡,那么他的能量值会减少 dd。初始时,他的能量值是 00,并且试运行期间他的能量值可以是负数。
而且大 Y 不会连续跑步打卡超过 kk 天;即不能存在 1≤x≤n−k1≤x≤n−k,使得他在第 xx 到第 x+kx+k 天均进行了跑步打卡。
小 T 同学在软件中设计了 mm 个挑战,第 ii(1≤i≤m1≤i≤m)个挑战可以用三个正整数 (xi,yi,vi)(xi,yi,vi) 描述,表示如果在第 xixi 天时,用户已经连续跑步打卡至少 yiyi 天(即第 xi−yi+1xi−yi+1 到第 xixi 天均完成了跑步打卡),那么小 T 同学就会请用户吃饭,从而使用户的能量值提高 vivi。
现在大 Y 想知道,在软件试运行的 nn 天结束后,他的能量值最高可以达到多少?
输入格式
本题的测试点包含有多组测试数据。
输入的第一行包含两个整数 cc 和 tt,分别表示测试点编号和测试数据组数。对于样例,cc 表示该样例与测试点 cc 拥有相同的限制条件。
接下来,对于每组测试数据:
- 输入的第一行包含四个正整数 n,m,k,dn,m,k,d,分别表示试运行的天数、挑战的个数、大 Y 单次跑步打卡的连续天数限制以及大 Y 跑步打卡减少的能量值。
- 接下来 mm 行,每行包含三个正整数 xi,yi,vixi,yi,vi,表示一次挑战。
输出格式
输出一行一个整数表示对应的答案。
输入输出样例
输入 #1
1 1 3 2 2 1 2 2 4 3 2 3
输出 #1
2
说明/提示
【样例解释 #1】
在第 1,21,2 天跑步打卡,第 33 天不跑步打卡,最终会获得 (−1)+(−1)+4=2(−1)+(−1)+4=2 的能量值。
【样例解释 #2】
该组样例满足测试点 33 的条件。
【样例解释 #3】
该组样例满足测试点 55 的条件。
【样例解释 #4】
该组样例满足测试点 1515 的条件。
【样例解释 #5】
该组样例满足测试点 1717 的条件。
【样例解释 #6】
该组样例满足测试点 1919 的条件。
【数据范围】
记 li=xi−yi+1li=xi−yi+1,ri=xiri=xi;
对于所有测试数据,保证:1≤t≤101≤t≤10,1≤k≤n≤1091≤k≤n≤109,1≤m≤1051≤m≤105,1≤li≤ri≤n1≤li≤ri≤n,1≤d,vi≤1091≤d,vi≤109。
测试点编号 | n≤n≤ | m≤m≤ | 特殊性质 |
---|---|---|---|
1,21,2 | 1818 | 102102 | 无 |
3,43,4 | 102102 | 102102 | 无 |
5∼75∼7 | 103103 | 103103 | 无 |
8,98,9 | 103103 | 105105 | 无 |
10,1110,11 | 105105 | 103103 | 无 |
12∼1412∼14 | 105105 | 105105 | 无 |
15,1615,16 | 109109 | 105105 | A |
17,1817,18 | 109109 | 105105 | B |
19∼2119∼21 | 109109 | 105105 | C |
22∼2522∼25 | 109109 | 105105 | 无 |
特殊性质 A:k≤102k≤102;
特殊性质 B:∀1≤i<m∀1≤i<m,ri<li+1ri<li+1;
特殊性质 C:∀1≤i<j≤m∀1≤i<j≤m,li<ljli<lj,ri<rjri<rj。
附件下载
run.zip 302B
二,思路
首先,看到题后马上想到此问题是可以用 dp 解决的,考虑设 dpi,jdpi,j 表示在第 ii 天已经连续跑步了 jj 天的最高能量值,则有如下转移,
dpi,j={maxj=0kdpi−1,j,j=0dpi−1,j−1−d,1≤j≤kdpi,j=⎩⎪⎨⎪⎧j=0maxkdpi−1,j,j=0dpi−1,j−1−d,1≤j≤k
再加上吃饭获得的能量值,于是立即得到 3636 分暴力 dp 做法。
我们观察这个转移,发现其可以归咎为以下操作,
- 取第 ii 天全局 maxmax 并填到 i+1i+1 的 dpi+1,0dpi+1,0 中;
- 将第 ii 天向右平移一位,并去掉最后一位;
- 对第 ii 天的一段后缀加上一个数。
发现这可以简单的使用平衡树维护,于是可以获得 5656 分,考场拿这分数差不多了因为在写就调不出来了。
再进一步,我们考虑 n≤109n≤109 该怎么做。
进一步观察 dp 式子,我们可以发现如下规律,
- dpi,0dpi,0 只可能在每次吃饭的下一天更新,如果吃饭后再跑一天则一定比吃完饭就更新劣;
- 在两顿饭之间,只有可能把数组向右平移一个位置,不会更新 dpi,0dpi,0,证明同上。
于是我们有以下想法,
- 将吃饭按 xx 从小到大排序,每次取出 xx 相同的一段处理最新的 dpdp 数组。
我们考虑如何维护这样一个 dpdp 数组。
我们使用两棵树,一颗记录所有可能的 dpi,0dpi,0,记为 T0T0;一颗记录当前的 dpidpi,记为 TT。
我们先列出所有需要的操作,
- 将所有元素的下标增加一个数;
- 删去下标大于某个值的所有元素;
- 对一段区间的某一元素加减一个数;
- 取一段区间的某一元素 maxmax;
- 取一段区间的每一元素的和;
- 删除某个区间。
我们先考虑维护要维护哪些位置,以下图为例,
考虑这样两次吃饭,x1=8,y1=3,v1=a1x1=8,y1=3,v1=a1 和 x2=10,y2=7,v2=a2x2=10,y2=7,v2=a2,
当第 88 天时,dpdp 变成了以下的样子,
当第 1010 天时,dpdp 变成了以下样子,
发现,dpdp 其实可以被以下情况概括,
即连续跑 5∼65∼6 天,获得 a1a1 能量值;连续跑 7∼117∼11 天,获得 a1+a2a1+a2 能量值。而且可以获知,最大值只会在这些位置和连续跑 00 天的位置取到,于是只维护吃饭获得能量的开始位置即可。
考虑如何算出总的能量值。
首先,发现从 0∼k0∼k 这些位置 −d−d 的贡献是一定的,于是只用在平移时全局加上 平移距离×(−d)平移距离×(−d) 即可。
其次,我们考虑每个位置由哪个 dpi,0dpi,0 转移而来。我们在每次吃完饭加入最大值时,在 T0T0 的最前面加入下标为 −1−1,权值为 TT 中最大能量值的元素,每次加上 vv 时,在 T0T0 中查找从其对应的 yy 开始的一段后缀的最大值加到当前位置的能量值即可。
最后,我们考虑每次加入新的 vv 时如何维护,发现,只要取位置小于等于 yy 的所有 vv 之和加到自己位置的能量值即可。当然,还要对位置大于 yy 的能量值加上 vv。
将算好的能量值添加到 TT 中即可,下标为 yy。
当然,最开始在 T0T0 中加入下标为 00,权值为 00 的元素与下标为 n+1n+1,权值为 −∞−∞ 的元素。
维护即可,可以得到 100100 分,略有卡常。
代码比较丑,请见谅。
三,代码
#include<bits/stdc++.h>
using namespace std;
#define int ll
typedef long long ll;
//AC_Hunter
#define mk make_pair
#define fi first
#define se second
typedef pair<int,int> pii;
typedef pair<double,double> pdd;
#define ReadIn(s) freopen(s,"r",stdin)
#define OutPut(s) freopen(s,"w",stdout)
//为了过题,读入输出有魔改的地方,请注意
namespace Fread {
const int SIZE=(1<<22)-1;char buf[SIZE],*S,*T;
inline char getchar() {if(S==T){T=(S=buf)+fread(buf,1,SIZE,stdin);if(S==T)return '\n';}return *S++;}
}
namespace Fwrite {
const int SIZE=(1<<22)-1;
char buf[SIZE],*S=buf,*T=buf+SIZE;
inline void flush(){fwrite(buf,1,S-buf,stdout);S=buf;}
inline void putchar(char c){*S++=c;if(S==T)flush();}
struct POPOSSIBLE{~POPOSSIBLE(){flush();}}ztr;
}
#define getchar Fread :: getchar
#define putchar Fwrite :: putchar
namespace Fastio{
struct Reader{
template<typename T>
Reader& operator >> (T& x) {
char c=getchar();
while(c<'0'||c>'9'){c=getchar();}x=0;
while(c>='0'&&c<='9'){x=x*10+(c-'0');c=getchar();}
return *this;
}
Reader& operator >> (char& c) {c=getchar();while(c==' '||c=='\n')c=getchar();return *this;}
Reader& operator >> (char* str) {
int len=0;
char c=getchar();
while(c==' '||c=='\n')c=getchar();
while(c!=' '&&c!='\n'&&c!='\r'){str[len++]=c;c=getchar();}
str[len]='\0';return *this;
}
Reader& operator >>(string &s){
int len=0;
char c=getchar();
while(c==' '||c=='\n')c=getchar();
while(c!=' '&&c!='\n'&&c!='\r'){s[len++]=c;c=getchar();}
s[len]='\0';return *this;
}
Reader(){}
}cin;
struct Writer{
template<typename T>
Writer& operator << (T x) {
if(x==0){putchar('0');return *this;}
static int sta[45];int top=0;
while(x){sta[++top]=x%10;x/=10;}
while(top){putchar(sta[top]+'0');--top;}
return *this;
}
Writer& operator << (char c) {putchar(c);return *this;}
Writer& operator << (const char* str){putchar(str[0]);return *this;}
Writer(){}
}cout;
}
#define cin Fastio :: cin
#define cout Fastio :: cout
const int M=2e5+1;
const int m998=998244353;
const int m107=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
//random_device seed;
mt19937 rd(time(0));
struct node {
signed son[2],key,ord;
int val,ma,dat,sum,tag1,tag2;
node(signed _k=0,signed _o=0,int _v=-inf,int _d=0) :
key(_k),ord(_o),val(_v),ma(_v),dat(_d),sum(_d){son[0]=son[1]=tag1=tag2=0;}
}tr[M<<1];
#define key(x) tr[x].key
#define o(x) tr[x].ord
#define val(x) tr[x].val
#define dat(x) tr[x].dat
#define sum(x) tr[x].sum
#define ma(x) tr[x].ma
#define ls(x) tr[x].son[0]
#define rs(x) tr[x].son[1]
#define tag1(x) tr[x].tag1
#define tag2(x) tr[x].tag2
signed tot,trash[M<<1],top;
inline void del(signed x) {
tr[x]=node();
trash[++top]=x;
}
inline signed New() {
return top?trash[top--]:++tot;
}
struct FHQ_Treap{
signed rt1,rt2;
inline void pushup(signed x) {
sum(x)=dat(x)+sum(ls(x))+sum(rs(x));
ma(x)=max(max(ma(ls(x)),ma(rs(x))),val(x));
}
inline void make(signed x,int t1,int t2) {
key(x)+=t1;tag1(x)+=t1;
val(x)+=t2;ma(x)+=t2;tag2(x)+=t2;
}
void pushdown(signed x) {
if(tag1(x)||tag2(x)) {
if(ls(x)) make(ls(x),tag1(x),tag2(x));
if(rs(x)) make(rs(x),tag1(x),tag2(x));
tag1(x)=tag2(x)=0;
}
}
signed merge(signed x,signed y) {
if(!x||!y) return x|y;
if(o(x)>o(y)) {
pushdown(y);
ls(y)=merge(x,ls(y));
pushup(y);
return y;
}
else {
pushdown(x);
rs(x)=merge(rs(x),y);
pushup(x);
return x;
}
}
void split(signed now,signed k,signed &x,signed &y) {
if(!now) return x=y=0,void();
pushdown(now);
if(key(now)<=k) x=now,split(rs(x),k,rs(x),y);
else y=now,split(ls(y),k,x,ls(y));
pushup(now);
}
void clear(signed x) {
if(ls(x)) clear(ls(x));
if(rs(x)) clear(rs(x));
del(x);
}
void insert1(signed k,int v,int d) {
signed id=New(),x,y;
tr[id]=node(k,rd(),v,d);
split(rt1,k,x,y);
rt1=merge(merge(x,id),y);
}
void insert2(signed k,int v) {
signed id=New(),x,y;
tr[id]=node(k,rd(),v,0);
split(rt2,k,x,y);
rt2=merge(merge(x,id),y);
}
void add_tree11(signed k,int t1,int t2) {
signed x,y;
split(rt1,k,x,y);
if(y) make(y,t1,t2);
rt1=merge(x,y);
}
inline void add_tree12(int t1,int t2) {
if(!rt1) return ;
make(rt1,t1,t2);
}
inline void add_tree2(int t1) {
if(!rt2) return ;
make(rt2,t1,0);
}
void del_tree(signed k) {
signed x,y;
split(rt1,k,x,y);
if(y) clear(y);
rt1=x;
}
int sum_tree(signed k) {
signed x,y;
split(rt1,k,x,y);
int res=sum(x);
return rt1=merge(x,y),res;
}
inline int get_max() {
return ma(rt1);
}
int back_max(signed k) {
signed x,y;
split(rt2,k-1,x,y);
int res=ma(y);
return rt2=merge(x,y),res;
}
void Init() {
if(rt1) clear(rt1);
if(rt2) clear(rt2);
rt1=rt2=0;
}
void print1(int x) {
if(!x) return ;
pushdown(x);
if(ls(x)) print1(ls(x));
cout<<x<<" "<<key(x)<<" "<<val(x)<<" "<<ma(x)<<" "<<dat(x)<<"\n";
if(rs(x)) print1(rs(x));
}
void print2(int x) {
if(!x) return ;
pushdown(x);
print2(ls(x));
cout<<x<<" "<<key(x)<<" "<<val(x)<<"\n";
print2(rs(x));
}
}Tr;
int T,c;
int n,m,k,d;
struct Up{
int x,y,v;
bool operator < (const Up &a) const {
return x<a.x;
}
}up[M];
signed main(){
// srand(time(0));
cin>>c>>T;
while(T--) {
cin>>n>>m>>k>>d;
for(signed i=1;i<=m;i++) cin>>up[i].x>>up[i].y>>up[i].v;
sort(up+1,up+m+1);
Tr.Init();
Tr.insert2(0,0);Tr.insert2(n+1,-inf);
int la=0;
for(signed i=1,j=0;i<=m;i=j+1) {
while(j+1<=m&&up[j+1].x==up[i].x) j++;
int de=up[i].x-la;
Tr.add_tree12(de,-d*de);
Tr.add_tree2(de);
Tr.del_tree(k);la=up[i].x;
for(signed t=i;t<=j;t++) {
int y=up[t].y,v=up[t].v;
if(y>k) continue;
int va=v-y*d+Tr.sum_tree(y)+Tr.back_max(y);
Tr.insert1(y,va,v);
Tr.add_tree11(y,0,v);
}
int Ma=Tr.get_max();
Tr.insert2(-1,Ma);
}
int de=n-la;
Tr.add_tree12(de,-d*de);
Tr.add_tree2(de);
Tr.del_tree(k);
int ans=max(Tr.back_max(0),Tr.get_max());
cout<<ans<<"\n";
}
return 0;
}
这里干了273行,完全不是一个在赛场上能完成的任务,所以给大家重新打篇短的
#include<bits/stdc++.h>
using namespace std;
#define int long long
//AC_Hunter
inline int read() {
int s=0,m=0;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')m=1;ch=getchar();}
while( isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return m?-s:s;
}
#define inf ((int)1e12)
int c,t,n,m,k,d,b[200005],cnt,f[200005][2];
struct QWQ {int l,r,v;} a[100005];
bool cmp(QWQ a1,QWQ a2) {return a1.r<a2.r;}
struct Node {int l,r,d,laz;};
struct Segment_Tree {
Node t[4*200005];
int ls(int p) {return p<<1;}
int rs(int p) {return p<<1|1;}
void pushup(int p) {t[p].d=max(t[ls(p)].d,t[rs(p)].d);}
void pushdown(int p) {
t[ls(p)].d+=t[p].laz,t[rs(p)].d+=t[p].laz;
t[ls(p)].laz+=t[p].laz,t[rs(p)].laz+=t[p].laz;t[p].laz=0;
}
void clear(int p,int l,int r) {
t[p].l=l,t[p].r=r,t[p].d=-inf,t[p].laz=0;
if(l==r) return;int m=(l+r)>>1;
clear(ls(p),l,m);clear(rs(p),m+1,r);
}
void update(int p,int l,int r,int v) {
if(l>r) return;
if(l<=t[p].l&&t[p].r<=r) {t[p].d+=v,t[p].laz+=v;return;}
pushdown(p);int m=(t[p].l+t[p].r)>>1;
if(l<=m) update(ls(p),l,r,v);
if(r>m) update(rs(p),l,r,v);pushup(p);
}
int query(int p,int l,int r) {
if(l<=t[p].l&&t[p].r<=r) return t[p].d;
pushdown(p);int s=-inf,m=(t[p].l+t[p].r)>>1;
if(l<=m) s=max(s,query(ls(p),l,r));
if(r>m) s=max(s,query(rs(p),l,r));return s;
}
} tt;
signed main() {
cin>>c>>t;
while(t--) {
cin>>n>>m>>k>>d;
b[cnt=1]=n;
for(int i=1;i<=m;i++) {
int x=read(),y=read();
b[++cnt]=a[i].l=x-y+1,b[++cnt]=a[i].r=x,a[i].v=read();
}
sort(b+1,b+cnt+1);int q=unique(b+1,b+cnt+1)-b-1;
tt.clear(1,1,200002);
memset(f,-0x3f,sizeof(f));f[0][0]=0;tt.update(1,1,1,inf);
for(int i=1;i<=m;i++)
a[i].l=lower_bound(b+1,b+q+1,a[i].l)-b,a[i].r=lower_bound(b+1,b+q+1,a[i].r)-b;
sort(a+1,a+m+1,cmp);
for(int i=1,j=1,r=1;i<=q;i++) {
tt.update(1,1,i-1,-d*(b[i]-b[i-1]));
while(b[i]-b[j]+1>k) j++;
while(r<=m&&a[r].r==i)
tt.update(1,1,a[r].l,a[r].v),r++;
f[i][0]=max(max(f[i-1][0],f[i-1][1]),0ll);
f[i][1]=tt.query(1,j,i)-d;
if(b[i+1]-b[i]==1) tt.update(1,i+1,i+1,inf+f[i][0]);
else tt.update(1,i+1,i+1,inf+max(f[i][0],f[i][1]));
}
printf("%lld\n",max(f[q][0],f[q][1]));
}
return 0;
}
祝大家在NOIP的赛场上能取得一个好名次
这完全不是一个小孩的代码量,求赞!