NOIP2023 T4 天天爱打卡 题解

NOIP2023 T4 题解

用到的算法

  • 离散化
  • 线段树
  • DP

思路与正解

看到这种题,莫名感觉要dp()

一个小结论

某一段打卡区间必然是从某一个挑战的起始日期开始,到某一挑战的结束日期为止

否则不是最优

这提示我们从挑战的开始与结束日期(事件天数)入手,设计dp

由于只关注开始与结束日期,中间相隔的大段时间没有用

故考虑把开始与结束日期离散化,用 t m p [ i ] tmp[i] tmp[i] 表示第 i i i个事件天数对应的正式日期

离散化的代码如下:

vector<int> tmp;
tmp.push_back(x), tmp.push_back(y);
sort(tmp.begin(),tmp.end());
tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());

尝试搞一搞dp

状态设计

因为打卡区间与起始和结束日期相关,考虑如下设计状态

f [ i ] [ 0 ] f[i][0] f[i][0] 表示 第 i i i个事件天数作为一个打卡区间的开始的最大能量值

f [ i ] [ 1 ] f[i][1] f[i][1] 表示 第 i i i个事件天数作为一个打开区间的结束的最大能量值

怎么转移捏?

对于 f [ i ] [ 0 ] f[i][0] f[i][0]

f [ i ] [ 0 ] = m a x ( 0 , f [ 1 ] [ 1 ] , f [ 2 ] [ 1 ] , . . . . . . f [ i − 2 ] [ 1 ] ( , f [ i − 1 ] [ 1 ] ) ) f[i][0] = max(0,f[1][1],f[2][1],......f[i-2][1](,f[i-1][1])) f[i][0]=max(0,f[1][1],f[2][1],......f[i2][1](,f[i1][1]))

t m p [ i ] − t m p [ i − 1 ] = = 1 tmp[i]-tmp[i-1]==1 tmp[i]tmp[i1]==1,即两个事件天数是相邻的,由于两个打卡区间要有间断处,所以不算上 f [ i − 1 ] [ 1 ] f[i-1][1] f[i1][1]这一项

否则就加上

m a x max max的操作可以用 p r i o r i t y _ q u e u e priority\_queue priority_queue维护

对于 f [ i ] [ 1 ] f[i][1] f[i][1]

考虑哪一个事件天数作为打卡区间的开始

由于题目 k k k的限制,计算出 m i n j minj minj 表示最小的与 i i i间隔不超过 k k k的事件天数,可以使用二分

f [ i ] [ 1 ] = m a x ( f [ j ] [ 0 ] + v a l [ j ] − c o s t [ j ] ) , j ∈ [ m i n j , i ] f[i][1] = max(f[j][0]+val[j]-cost[j]),j\in[minj,i] f[i][1]=max(f[j][0]+val[j]cost[j]),j[minj,i]

v a l [ j ] val[j] val[j] 表示挑战落在事件天数 [ j , i ] [j,i] [j,i]内的能量值

c o s t [ j ] cost[j] cost[j]表示第 i ∼ j i\sim j ij天打卡消耗的能量值

这是很经典的可以用线段树优化的形式

具体地,令 t r [ i ] = f [ i ] [ 0 ] + v a l − c o s t tr[i] = f[i][0] + val - cost tr[i]=f[i][0]+valcost

v a l val val c o s t cost cost的值伴随着计算的过程逐步修改

线段树维护的过程伪代码如下:

build();//建立一棵维护最大值得线段树 days为事件天数的总数
f[1][0] = 0;
for(i from 1 to days)
{
    rangeadd(i,i,f[i][0]);//线段树区间加操作,range(ql,qr,val) 对从ql-qr的区间+val
    对于每个结束日期为i的挑战://维护val
    {
        rangeadd(1,il,val);//il为起始日期对应的事件天数
	}
    计算代价:
    {
        rangeadd(1,i-1,-d*(tmp[i]-tmp[i-1]));
        //对于第i天以前的事件天数,补上从tmp[i-1]+1~tmp[i]的能量消耗
        rangeadd(i,i,-d);
        //对于第i天,只需减掉当天的能量消耗即可
	}
    计算min_j
    f[i][1] = query(min_j,i);//线段树查询操作,query(ql,qr) 从ql-qr区间查询
    更新f[i+1][0]的值
}
ans = max(f[i][1]);//i from 1 to days

最大复杂度: 2 m l o g 2 m 2mlog2m 2mlog2m

code

#include<bits/stdc++.h>
#define N 200100
#define ll long long
#define k1 k<<1
#define k2 k<<1|1
using namespace std;

typedef struct
{
	int l,r,il,ir,v;
}run;

bool cmp(const run &a, const run &b)
{
	return a.ir < b.ir;
}

ll f[N][2];
ll tr[N<<2], lz[N<<2];

void build(int k,int l,int r)
{
	if(l==r)
	{
		tr[k] = -1e18;
		lz[k] = 0;
		return;
	}
	int m = (l+r)>>1;
	build(k1,l,m);
	build(k2,m+1,r);
	tr[k] = -1e18;
	lz[k] = 0;
}

void update(int k)
{
	tr[k] = max(tr[k1],tr[k2]);
}

void modify(int k,ll val)
{
	tr[k] += val;
	lz[k] += val;
}

void pushdown(int k)
{
	modify(k1,lz[k]);
	modify(k2,lz[k]);
	lz[k] = 0;
}

ll query(int k,int l,int r,int ql,int qr)
{
	if(ql<=l&&qr>=r) return tr[k];
	if(lz[k]) pushdown(k);
	int m = (l+r)>>1;
	ll ret = -1e18;
	if(ql<=m) ret = max(ret,query(k1,l,m,ql,qr));
	if(qr>m) ret = max(ret,query(k2,m+1,r,ql,qr));
	update(k);
	return ret;
	
}

void pointmodify(int k,int l,int r,int p,ll val)
{
	if(l==r)
	{
		tr[k] = val;
		return; 
	}
	if(lz[k]) pushdown(k);
	int m = (l+r)>>1;
	if(p<=m) pointmodify(k1,l,m,p,val);
	else pointmodify(k2,m+1,r,p,val);
	update(k);
}

void rangeadd(int k,int l,int r,int ql,int qr,ll val)
{
	if(ql<=l&qr>=r)
	{
		modify(k,val);
		return;
	}
	if(lz[k]) pushdown(k);
	int m = (l+r)>>1;
	if(ql<=m) rangeadd(k1,l,m,ql,qr,val);
	if(qr>m) rangeadd(k2,m+1,r,ql,qr,val);
	update(k);
}

void solve()
{
	int n,m,k,d;
	cin>>n>>m>>k>>d;
	vector<run> r(m);
	vector<int> tmp;
	tmp.push_back(0); 
	for(int i = 0;i<m;i++)
	{
		int x,y,v;
		cin>>x>>y>>v; 
		r[i].l = x-y+1, r[i].r = x, r[i].v = v;
		tmp.push_back(x-y+1), tmp.push_back(x);
	}
	sort(tmp.begin(),tmp.end());
	tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
	int days = tmp.size()-1;
	for(int i = 0;i<m;i++)
	{
		r[i].il = lower_bound(tmp.begin(),tmp.end(),r[i].l)-tmp.begin();
		r[i].ir = lower_bound(tmp.begin(),tmp.end(),r[i].r)-tmp.begin();
	}
	sort(r.begin(),r.end(),cmp);
	build(1,1,days);
	f[1][0] = 0;
	int index = 0;
	priority_queue<ll> bg;
	bg.push(0);
	for(int i = 1;i<=days;i++)
	{
		pointmodify(1,1,days,i,f[i][0]);
		while(index<m&&r[index].ir<i) index++;
		while(index<m&&r[index].ir==i)
		{
			rangeadd(1,1,days,1,r[index].il,r[index].v);
			index++; 
		}
		if(i>1) rangeadd(1,1,days,1,i-1,-1ll*d*(tmp[i]-tmp[i-1]));
		rangeadd(1,1,days,i,i,-d);
		int j = upper_bound(tmp.begin(),tmp.end(),max(0,tmp[i]-k))-tmp.begin();//
		f[i][1] = query(1,1,days,j,i);
		if(tmp[i+1]-tmp[i]>1) 
		{
			bg.push(f[i][1]);
			f[i+1][0] = bg.top();
		}
		else
		{
			f[i+1][0] = bg.top();
			bg.push(f[i][1]);
		}
	}
	cout<<bg.top()<<endl;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int c,t;
	cin>>c>>t;
	while(t--)
	{
		solve();
	}
}

一道类似的题目

CF115E

  • 40
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOIP 2018 普及组初赛第1028题的题解如下: 题目描述: 给定一个正整数N,要求编写一个程序,计算出它的阶乘N!。阶乘N!是所有小于或等于N的正整数的乘积,且0!定义为1。例如:5! = 5 × 4 × 3 × 2 × 1 = 120。 输入描述: 输入仅包含一个正整数N,其范围为1到20。 输出描述: 输出为计算得到的阶乘N!的值。 解题思路: 1. 使用一个数组来存储中间计算结果。 2. 从1开始,依次计算到N的所有整数的阶乘。 3. 每计算出一个数的阶乘,就将其乘到数组中,更新数组的值。 4. 最终数组存储的就是N!的结果。 注意点: - 由于N的范围为1到20,而20!的结果是一个非常大的数,普通的数据类型无法存储,因此需要使用数组来模拟大数运算。 - 在实现大数乘法时,需要注意进位的问题。 以下是一个简化的伪代码示例: ``` 输入:N 创建一个足够大的数组result用于存储结果 result[0] = 1 // 初始化结果为1 对于i从1到N: carry = 0 // 进位初始化为0 对于j从0到result的长度减1: temp = result[j] * i + carry result[j] = temp % 10 // 更新当前位的值 carry = temp / 10 // 计算新的进位 结束循环 如果carry不为0,则继续添加进位 结束循环 输出result数组(从后往前输出,以得到正确的顺序) ``` 实际编程时,需要注意数组的索引处理和进位处理,以及在输出时避免在前面输出不必要的零。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值