[NOI2017]蔬菜

137 篇文章 1 订阅
89 篇文章 0 订阅

题目

传送门 to luogu

思路一

感谢@Tyher 的博客提供了思路。

当然咯,我也有想过“时光倒流”,不过最后没有成型罢了。

我们从后往前考虑每一天的行为。感觉和 “图的连通性” 问题中,只有删边时类似。

对于最后一天来说,我现在可以随便卖 m m m 种蔬菜,因为 蔬菜只会源源不断地增加,不会减少。也就是说,现在能卖的,“以后”还是能卖。所以可以贪心地选择价值最大的蔬菜。

用堆来维护即可。第一次卖出之前,将价值设置为 ( s i + a i ) (s_i+a_i) (si+ai) ,其他情况下为 a i a_i ai

这个设置是没有问题的唷!因为这个价值 ( s i + a i ) (s_i+a_i) (si+ai) 即使我在“以后”卖掉也不会变。——我反正是没有想到这一点。

然而 k k k 最大可达 1 0 5 10^5 105 (毕竟 p j p_j pj 互不相同)。这提示我们离线处理出所有 p j p_j pj 的情况。

如何递推呢?从 ( p + 1 ) (p+1) (p+1) 天的情况转移到 p p p 天的情况?

只需要强制总卖出数量降为 m p mp mp 即可。并且无论是哪种蔬菜都可以,因为蔬菜可以卖的更早。你只需要将空腾出来,然后依次填上即可。

似乎有点晦涩。仔细想想 “从后往前考虑” 的情形,你就会明白的!不妨想象成 p × m p\times m p×m 的棋盘上放置棋子,有一些棋子只能放在 1 1 1 r r r 行。留空总有人填

思路二

感谢@ustze 的博客提供了思路。

贵的菜要多卖。毫无争议的一句话。什么时候卖呢?越晚越好

这很像 不守交规 啊!所以贪心地将蔬菜按照权值排序,并查集查询最晚的可售卖日期。

第一次售卖的蔬菜,将权值赋值为 ( s i + a i ) (s_i+a_i) (si+ai) 即可。排序利用堆即可。

问题又是如何递推。但是它的递推十分简单。就像 思路一 中所说,你只需要随便拿走几个棋子,腾出空来即可。我拿走的棋子,毫无疑问,应该是最后出堆的元素(因为其权值最小)。所以用数组记录一下,每 m m m 次出堆就记录一次。

代码

此处实现了 思路二条条大路通罗马,无有一径至我家。

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
# define MuBan template < class T >
MuBan void getMin(T &a,const T &b){ if(b < a) a = b; }
MuBan void getMax(T &a,const T &b){ if(a < b) a = b; }

const int MaxN = 1000005;
int fa[MaxN]; /* 此题并没有严格要求O(αn),O(n log n)也可以过 */
inline int findSet(int a){
	return fa[a] = (fa[a] == a ? a : findSet(fa[a]));
}
int a[MaxN], c[MaxN], x[MaxN];
priority_queue<pair<int,int>> pq;
long long ans[MaxN]; int cnt[MaxN];
# define MP make_pair
int main(){
	int n = readint(), m = readint(), T = readint();
	for(int i=1,s; i<=n; ++i){
		a[i] = readint(), s = readint();
		c[i] = readint(), x[i] = readint();
		if(c[i]) pq.push(MP(a[i]+s,i));
	}
	const int MaxP = 100000;
	for(int i=0; i<=MaxP; ++i)
		cnt[i] = m, fa[i] = i;
	for(int i=1,day; i<=MaxP; ++i){
		ans[i] = ans[i-1];
		for(int buys=m; buys and not pq.empty(); ){
			auto it = pq.top(); pq.pop(); int j = it.second;
			day = min((x[j]?(c[j]-1)/x[j]+1:MaxP),MaxP);
			day = findSet(day); if(day == 0) continue;
			-- cnt[day], -- c[j], ans[i] += it.first, -- buys;
			if(c[j] != 0) pq.push(MP(a[j],j));
			if(cnt[day] == 0) fa[day] = day-1;
		}
	}
	while(T --)
		printf("%lld\n",ans[readint()]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值