CF1945F. Kirill and Mushrooms
题意:给n种蘑菇和一个排列p,每个蘑菇有一个魔力值,可以按任意顺序采集蘑菇,采集的价值为采集的蘑菇数量采集的蘑菇中的最小魔力值。如果采集k*个蘑菇,那么排列中 p 1 , p 2 , ⋯ , p k − 1 p_1,p_2, \cdots,p_{k-1} p1,p2,⋯,pk−1的蘑菇的魔力值都会变成0,你不会拿魔力值为0 的蘑菇,问最多能采集多少价值,且在最大价值的前提下找到采集蘑菇数量最小的一种方式。
思路:可以用对顶堆来做,一个大根堆维护将要采集的蘑菇,一个小根堆维护已经采集的蘑菇,每次采集蘑菇都看一下当前蘑菇魔力值会不会变成0即可,无论在哪一个堆的蘑菇的魔力值变成0,都将这个蘑菇弹出堆。之后从一开始遍历k,即可得到答案。
其实本质上就是将蘑菇的法力值从大到小排序,然后遍历每个蘑菇能不能取。
代码如下:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <set>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--){
int n;cin>>n;
vector<ll>a(n+1),p(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>p[i];
multiset<ll,less<ll>>picked;
multiset<ll,greater<ll>>to_pick;
for(int i=1;i<=n;i++)to_pick.insert(a[i]);
int k=1;
pii ans={0,0};
while(to_pick.size())
{
if(k>=2)
{
if(picked.find(a[p[k-1]])!=picked.end())picked.erase(picked.find(a[p[k-1]]));
else to_pick.erase(to_pick.find(a[p[k-1]]));
}
if(k+k-1>n)break;//选的蘑菇和魔力变为0的蘑菇大于n就可以结束了
while(picked.size()<k)//此时采集的蘑菇不足k个,要从待采集的蘑菇中选取
{
picked.insert(*to_pick.begin());
to_pick.erase(to_pick.begin());
}
ll num=k*(*picked.begin());
if(ans.first==num){
ans.second=min(ans.second,k);
}else if(ans.first<num){
ans={num,k};
}
k++;
}
cout<<ans.first<<" "<<ans.second<<"\n";
}
return 0;
}
CF1945G. Cook and Porridge
题意:n名学生准备喝粥,有一个厨师将在D分钟内给他们打粥,每一分钟为一名同学打粥,每名同学都有一个吃粥时间s,如果第i个学生在x分钟开始时吃到一份粥,那么他将在第
x
+
s
i
x+s_i
x+si分钟结束时回到队列中排队,同时每名学生都有一个优先值,当他回到队列的时候,他会站到第一个大于等于他的优先值的同学后面,也就是他会插队,如果有几个同学同时返回队列,他们会按
s
i
s_i
si的升序返回队列。问在D分钟内,所有的学生是否至少都能吃一次粥。
思路:因为每分钟最多只给一个同学打粥,因此按题意模拟即可,用一个优先队列维护插队的同学,在用一个vector记录下来每个时刻插队的人的编号,但是这里插队也是有优先级的,即:
1、优先级高的同学排在前面
2、回到队列的时间靠前的同学排在前面
3、吃粥时间
s
i
s_i
si小的同学排在前面
优先级大小为
1
>
2
>
3
1>2>3
1>2>3
学到了用优先队列可以插入array数组,改变数组元素的正负号即可同时按大或小排序
接着按题意模拟即可:有一些小技巧,具体看代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <set>
#include <iomanip>
#include <numeric>
#include <array>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <set>
#include <iomanip>
#include <numeric>
#include <array>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--)
{
int n,D;cin>>n>>D;
vector<ll>k(n+2),s(n+2);
for(int i=1;i<=n;i++)cin>>k[i]>>s[i];
auto suf=k;
for(int i=n;i>=1;i--)suf[i]=max(suf[i],suf[i+1]);
priority_queue<array<int,4>>q;//插队队列
vector<vector<int>>pos(D+1);//该时间插入的人
int cur=1;//当前第一个没吃饭的人
int ans=-1;
for(int i=1;i<=D;i++){
if(!q.empty()&&q.top()[0]>suf[cur]){//如果能插队
auto [p,t,s,id]=q.top();q.pop();
s=-s;
if(i+s<=D){
pos[i+s].push_back(id);
}
}
else{//如果没人插队,那么就可以顺延一个人吃粥
if(i+s[cur]<=D){
pos[i+s[cur]].push_back(cur);
}
cur++;
}
if(cur==n+1){//说明每个人都至少吃过一次粥了
ans=i;
break;
}
for(auto id:pos[i]){
array<int,4>b={k[id],-i,-s[id],id};
q.push(b);//这里有个小细节,因为是大根堆,回到队列的时间和吃粥的时间用负数,就可以保证是从小到大排序
}
}
cout<<ans<<"\n";
}
return 0;
}
···