• [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;
    }
    
  • 相关阅读:
    Scripts.Render 索引超出了数组界限
    AutoFac使用方法总结
    MFC中打开一个获取路径的对话框
    MFC中自定义消息
    获得窗口句柄的方法
    App Doc View Frame中指针的获取
    MFC中菜单的命令响应顺序
    机械设计软件编写心得
    对SVD奇异值分解的理解
    安装win8+Ubuntu14.04双系统的经验总结
  • 原文地址:https://www.cnblogs.com/mrclr/p/10822743.html
Copyright © 2020-2023  润新知