考虑到两个大于 的奶茶组合起来和单独购买浪费的钱数是一样的(虽然本题中无法单独购买奶
茶),想要省钱,那么应该考虑组合两个小于 的奶茶,或者组合一个大于 的(下文简称“大
的”),一个小于 的(下文简称“小的”)。
贪心思路应该是优先将大的和小的匹配进行购买,如果匹配结束后还有多余的“小的”,那么将“小的”之间两两匹配;如果匹配结束之后还有多余的“大的”,那么无法继续匹配。
考虑大的和小的进行匹配的过程,恰好小于 的奶茶只能和恰好大于 的奶茶匹配。例如
的时候,4 只能和 6 匹配,如果和 7、8、9 匹配,则浪费的钱数和单独购买相同,导致无意义贪心。而价格为 1 的奶茶可以和 6、7、8、9 匹配,我们可以感觉到,所有小于 的奶茶中,越接近 的奶茶匹配的选择越少,所以应该被优先匹配。
最终思路:将所有“小的”从大到小排序,然后开始匹配,匹配的时候将所有“大的”从小到大进行匹配,类似双指针维护匹配过程。最终再判断是否还有多余的“小的”,将这些“小的”两两匹配。(随便怎么匹配都是一样的)
#include <iostream>
#include <set>
using namespace std;
#define ll long long
const int maxn = 1e5;
set<pair<int, int> > s;
int main(){
ll n, m, a, b, ans = 0;
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a >> b;
b %= m;
if(b == 0) continue;
b = m - b;
ans += b * a;
s.insert(make_pair(b, a));
}
auto r = s.lower_bound(make_pair(m/2, 0)), l = r;
l--;
ll rcnt = (*r).second, lcnt = 0;
if(r != s.begin()){
lcnt = (*l).second;
}else{
ll allcnt = 0;
while(r != s.end()){
allcnt += rcnt;
r++;
rcnt = (*r).second;
}
ans -= allcnt / 2 * m;
cout << ans << endl;
return 0;
}
ll costsum = 0;
if((*r).first == m / 2){
r++;
rcnt = 0;
if(r != s.end())
rcnt = (*r).second;
}
while(true){
if(!rcnt){
r++;
if(r == s.end()) break;
rcnt = (*r).second;
}
if(!lcnt){
if(l == s.begin()) break;
l--;
lcnt = (*l).second;
}
while((*l).first + (*r).first < m && r != s.end()){
r++;
rcnt = (*r).second;
}
if(r == s.end()) break;
ll now = min(lcnt, rcnt);
lcnt -= now; rcnt -= now;
costsum += now;
ans -= now * m;
}
ll allcnt = 0;
r = s.lower_bound(make_pair(m/2, 0));
while(r != s.end()){
allcnt += (*r).second;
r++;
rcnt = (*r).second;
}
ans -= (allcnt - costsum) / 2 * m;
cout << ans << endl;
}