P9871 [NOIP2023] 天天爱打卡 这个代码量根本不是一个小孩能承受的......

今天是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,21818102102
3,43,4102102102102
5∼75∼7103103103103
8,98,9103103105105
10,1110,11105105103103
12∼1412∼14105105105105
15,1615,16109109105105A
17,1817,18109109105105B
19∼2119∼21109109105105C
22∼2522∼25109109105105

特殊性质 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={max⁡j=0kdpi−1,j,j=0dpi−1,j−1−d,1≤j≤kdpi,j​=⎩⎪⎨⎪⎧​j=0maxk​dpi−1,j​,j=0dpi−1,j−1​−d,1≤j≤k​

再加上吃饭获得的能量值,于是立即得到 3636 分暴力 dp 做法。

我们观察这个转移,发现其可以归咎为以下操作,

  • 取第 ii 天全局 max⁡max 并填到 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。

我们先列出所有需要的操作,

  • 将所有元素的下标增加一个数;
  • 删去下标大于某个值的所有元素;
  • 对一段区间的某一元素加减一个数;
  • 取一段区间的某一元素 max⁡max;
  • 取一段区间的每一元素的和;
  • 删除某个区间。

我们先考虑维护要维护哪些位置,以下图为例,

考虑这样两次吃饭,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的赛场上能取得一个好名次

这完全不是一个小孩的代码量,求赞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值