其他分享
首页 > 其他分享> > 【题解】 CF767E Change-free 带悔贪心

【题解】 CF767E Change-free 带悔贪心

作者:互联网

Legend

Link \(\textrm{to Codeforces}\)。

你有 \(m\) 枚 \(1\) 元硬币,\(\infty\) 张 \(100\) 元纸币。收银员有 \(\infty\) 枚 \(1\) 元硬币和 \(\infty\) 张 \(100\) 元纸币。

接下来的 \(n\) 天,第 \(i\) 天你要买价格为 \(c_i\) 元的物品,你可以选择以任一方法支付。

收银员找零时会找给你数量尽量少的钱(即贪心找 \(100\) 再找 \(1\))。

如果收银员在第 \(i\) 天找给你 \(k\) 元,那么他会有 \(kw_i\) 的不满意度。

请最小化收银员 \(n\) 天的不满意度总和,并输出每一天的支付方案。

\(1 \le n ,c_i ,w_i \le 10^5\),\(1 \le m \le 10^9\)。

Editorial

Observation 1:我们的给钱策略与收银员的找钱策略相同,都是贪心使用 \(100\) 元。

显然,因为 \(100\) 元有无数张,\(1\) 元是有限的,且 \(100 \bmod 1 = 0\)。

所以我们最初的时候可以直接把 \(c_i\) 对 \(100\) 取模。特别地 \(c_i \bmod 100 = 0\) 的物品我们此后无需考虑

注意,接下来的讨论都是针对于 \(1 \le c_i < 100\) 的。

Observation 2:支付方式要么使用一张 \(100\),要么就要用 \(1\) 元硬币支付全款。

显然,如果单独使用一张 \(100\) 都会造成找零,如果还使用更多的 \(100\) 元,则平白无故增加了找零数量。

并且由于收银员的找零策略,这样做并不会获得更多的 \(1\) 元,而是会返还 \(100\) 元,是冗余操作。

如果同时使用一张 \(100\) + 一些 \(1\) 元,那么这些 \(1\) 元造成更多的找零,而且也不会获得更多的 \(1\) 元。

Observation 3:如果我们选择用 \(1\) 元硬币支付全款,那么把这次支付更换为用一张 \(100\) 元支付,二者支付后的 \(1\) 元硬币数量差恒为 \(100\)。

这是一个非常重要的性质,而且非常显然(不过我居然没有注意到)。

有了这个性质之后就可以开始带悔贪心了。

首先对于每一天我们都看看能不能直接用 \(1\) 元支付完成,如果可以,直接支付,本次支付不会增加不满意度。

如果不行,则找到之前某一次【直接用 \(1\) 元支付完成】的交易,将它换成用 \(100\) 元支付,

这样就可以使得当前硬币数量增加 \(100\),就可以用若干张 \(1\) 元完成本次支付了。

那么这个某一次是哪一次呢?就是用 \(100\) 元支付造成不满意度最小的那次,用一个堆维护就好了。

如果【不满意度最小的那次】的不满意度还要比当前大,就直接用 \(100\) 元完成这次支付就可以了。

时间复杂度 \(O(n \log n)\)。

Code

注意特判 \(c_i \bmod 100=0\) 的支付。

#include <bits/stdc++.h>

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long
#define pb push_back
#define uint unsigned int

const int MX = 1e6 + 23;
const LL MOD = 998244353;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

int n ,m ,c[MX] ,w[MX];
int a[MX] ,b[MX];

struct Choice{
	int w ,id;
	bool operator <(const Choice &B)const{
		return w > B.w;
	}
};

int cho[MX];

void solve(){
	n = read() ,m = read();
	for(int i = 1 ; i <= n ; ++i){
		c[i] = read();
		a[i] = c[i] / 100;
		b[i] = c[i] % 100;
		if(!b[i]) cho[i] = 1;
	}
	// 对于一个人,你要么用 1 元给满,要么干脆就不给 1 元
	// 1. 给多了还要找钱,不如给正好,
	// 2. 给少了不如不给,因为给了找零更多,但最后的 1 元数量是一样的,不满意度还更高
	//
	// 只要确定每个人是给还是不给就好了。
	//
	// 所以容易想到一个贪心策略:对于每一个人先贪心地给 1¥,不够则对前面的方案进行调整:
	// 1. 尝试前面某一个人不给钱,把钱省下来(但是会增加不满意度)
	// 草所以一定会省下来 100 元。。。。。。。。。。。。直接贪心就行了 /fad

	LL ans = 0;
	std::priority_queue<Choice> q;
	for(int i = 1 ; i <= n ; ++i){
		w[i] = read();
		if(m >= b[i]){
			if(b[i]){
				q.push((Choice){(100 - b[i]) * w[i] ,i});
			}
		}
		else{
			if(!q.empty() && q.top().w <= (100 - b[i]) * w[i]){
				ans += q.top().w;
				q.pop();
				q.push((Choice){(100 - b[i]) * w[i], i});
			}
			else{
				ans += (100 - b[i]) * w[i];
			}
			m += 100;
		}
		m -= b[i];
	}

	printf("%lld\n" ,ans);
	while(!q.empty()){
		cho[q.top().id] = 1;
		q.pop();
	}
	for(int i = 1 ; i <= n ; ++i){
		if(cho[i]){
			printf("%d %d\n" ,a[i] ,b[i]);
		}
		else printf("%d %d\n" ,a[i] + 1 ,0);
	}
}

int main(){
	int T = 1;
	for(int i = 1 ; i <= T ; ++i){
		solve();
	}
	return 0;
}

标签:le,带悔,收银员,int,题解,free,支付,100,define
来源: https://www.cnblogs.com/imakf/p/13945505.html