[NOI2017]蔬菜

嘟嘟嘟


这题搞了将近一个下午,总算给搞懂了。
真心觉得NOI2017比NOI2018难多了(貌似有目共睹?)。


这题拿到后就觉得是一个贪心,但是当时就想着正着推,每一次找价值最大的卖,然后如果到了一个截止日期就把一些卖出去“收回来”换成一些价值小的蔬菜,好挣他们的第一笔前\(s_i\),总的来说就是价值小的蔬菜尽量在临近保质期的时候卖。但这样得具体算出来把哪些换走,这又得考虑换走的蔬菜在保质期内能否再卖出去等一系列巨多问题。简直就是一道大模拟。感觉暴力分都不知道怎么拿……
最后还是看了题解。
这一点我也不知道是怎么想出来的:考虑“时光回流”,即从最后一天往前卖。这样我们就可以贪心的从价值大的蔬菜开始卖了!。
但是这样似乎对于每一个\(p_j\)都得推一遍?其实不然,我们把所有卖出去的蔬菜记录下来,因为第\(p\)天卖出去,第\(p - 1\)天必定能卖出去,所以我们再往回推一遍,每减少一天,就把价值最小的一些蔬菜收回来不卖,使收益损失最少,从而保持前一天的收益最大。


代码照学姐抄的,看了好久才懂。
首先对于额外收益,我们单独看成新的一种蔬菜,且这种蔬菜只有一个,价值是\(a_i + s_i\)
然后如果第\(t_i\)天结束时第\(i\)种蔬菜全部腐烂,记这一天还剩\(c_i\)个,则第\(j(j < t_i)\)天就有第\(i\)种蔬菜\(c_i + (t_i - j) * x_i\)个。
每一种蔬菜开始卖的时候,我们要找在他腐烂之前且最晚的一天,满足这一天还没卖到上限开始卖。一天天暴力找的话会超时,这时候用并查集把所有卖完的连续天数合在一块儿,代表元是最小的那一天。然后查询就是\(O(1)\)的了。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e6 + 5;
const int M = 1e5 + 5;
inline ll read()
{
	ll ans = 0;
	char ch = getchar(), last = ' ';
	while(!isdigit(ch)) last = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(last == '-') ans = -ans;
	return ans;
}
inline void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, m, K;
struct Node
{
	int x, val, c, t;		//这里的c表示全腐烂当天还剩几个 
	In bool operator < (const Node& oth)const
	{
		return val > oth.val;
	}
}veg[maxn << 1];
int num[maxn], sol[maxn * 10], tot = 0;
ll ans[maxn];

int p[maxn];
In int Find(int x) {return x == p[x] ? x : p[x] = Find(p[x]);}

int main()
{
	n = read(), m = read(), K = read();
	for(int i = 1; i <= n; ++i) 
	{
		int a = read(), s = read(), c = read(), x = read();
		if(!x)
		{
			veg[i] = (Node){x, a, c - 1, M};
			veg[i + n] = (Node){0, a + s, 1, M};
		}
		else
		{
			int t = (c + x - 1) / x; 
			veg[i] = (Node){x, a, c - (t - 1) * x - 1, t};
			veg[i + n] = (Node){0, a + s, 1, t};
		}
	}
	sort(veg + 1, veg + n * 2 + 1);
	for(int i = 1; i <= M; ++i) p[i] = i;
	for(int i = 1; i <= (n << 1); ++i)
	{
		int s = Find(veg[i].t);		//从<=veg[i].t且第一个没填满的一天往回填 
		int used = 0;
		while(s && used < veg[i].c + veg[i].x * (veg[i].t - 1))	//直到每一天都卖完或者这种蔬菜全卖光 
		{
			int now = veg[i].c + veg[i].x * (veg[i].t - s);
			now -= used;
			while(now && num[s] < m)	//直到这一天卖完或蔬菜全卖光 
			{
				--now, ++used, ++num[s];
				sol[++tot] = veg[i].val;
			}
			if(num[s] == m) p[s] = Find(s - 1);
			s = Find(s - 1);
		}
	}
	sort(sol + 1, sol + tot + 1);
	for(int i = 1; i <= tot; ++i) ans[M] += sol[i];
	int st = 1, sum = 0;
	for(int i = 1; i <= M; ++i) sum += num[i];
	for(int i = M - 1; i; --i)
	{
		ans[i] = ans[i + 1];
		int del = max(sum - i * m, 0);
		sum -= del;
		for(int j = st; j <= st + del - 1; ++j) ans[i] -= sol[j];
		st += del;
	}
	for(int i = 1; i <= K; ++i) write(ans[read()]), enter;
	return 0;
}
posted @ 2019-05-06 23:18  mrclr  阅读(232)  评论(0编辑  收藏  举报