原题链接:https://codeforces.com/problemset/problem/1945/F
题目描述
当营地里的每个人都进入梦乡后,基里尔便偷偷溜出帐篷,到智慧的橡树下采蘑菇。
众所周知,橡树下生长着 n 朵蘑菇,每朵蘑菇都有 vi 的魔力。 基里尔非常想用这些蘑菇制作一种魔力最大的灵药。
灵药的强度等于其中蘑菇的数量与这些蘑菇中最小魔力的乘积。要配制灵药,基里尔要依次采摘生长在橡树下的蘑菇。基里尔可以按照任何顺序采集蘑菇。
然而,事情并非如此简单。智慧的橡树给出了一个排列 p1,p2,...,pn,如果基里尔只采摘 k 朵蘑菇,那么 vp1,vp2,...,vpk−1 都将变为 0。 基里尔不会使用魔力为零的蘑菇来配制灵药。
你的任务是帮助基里尔采集蘑菇,使他能够酿造出最大魔力的灵药。然而,基里尔有点害怕在橡树旁待太久,所以在所有适合采集蘑菇的方案中,他要求你找到蘑菇数量最少的那个。
输入格式
每个数据包含多个测试用例。
第一行包含一个整数 t ( 1≤t≤10^4 ) ,表示测试用例的数量。
每个测试用例的第一行都包含一个整数 n(1≤n≤2⋅10^5),表示蘑菇的数量。
第二行包含一个大小为 n 的数组 v(1≤vi≤10^9),表示蘑菇的魔力。
第三行包含一个长度为 n 的排列 p。
保证所有测试用例中的 n 的值之和不超过 2⋅10^5。
输出格式
对于每个测试用例,输出两个用空格隔开的整数,分别为可以酿造的灵药的最大浓度和基里尔为此需要使用的最少蘑菇数量。
提示
在样例的第一个测试用例中,你需要采摘前两朵蘑菇,因此灵药的魔力等于 2⋅min(a1,a2)=2⋅min(9,8)=2⋅8=16。 请注意,采摘两朵蘑菇后,第三朵蘑菇的魔力将变为 0。
输入输出样例
输入
6
3
9 8 14
3 2 1
5
1 2 3 4 5
1 2 3 4 5
6
1 2 3 4 5 6
6 5 4 3 2 1
5
1 4 6 10 10
2 1 4 5 3
4
2 2 5 5
4 2 3 1
5
1 2 9 10 10
1 4 2 3 5
输出
16 2
9 3
8 2
20 2
5 1
20 2
解题思路:
首先更形象的描述一下题意,假设我们选择k个数,那么就会有k-1数变为0并且是p数组前k-1位置描述的k-1个位置变为0,那么对于v(p(0)),v(p(1)),...v(p(k-1))就会全部变成0,根据题意,变成0之后就不能选择了,然后我们就只能从除了这k-1个位置之外的其他位置选择数了,然后要求我们在能获取最大魔力的基础上,选择最少的位置。
实际上我们每次枚举选择k个数之后,删除的k-1个数应该是p数组前k-1位置所描述的位置的数,也就是我们选择的数实际上只能从p数组前k-1个位置之后的所有位置中选择,根据题意我们知道最大魔力=选择的数的数量*选择的数中魔力最小的那个蘑菇的魔力,我们枚举选择了k个数,实际上就是要维护可以选择的那些数中最大的k个数,那么实际上就是需要维护一个大小为k的小根堆,用于维护后面可以选择的数中最大的k个数,此时选择的数中的最小的元素就是堆顶元素,为了方便处理,我们考虑从大到小枚举要选择的元素个数i,那么堆里面的元素个数除了每次加入新的元素v[p[i]]之外,由于我们枚举的是选择i个元素,所以我们的堆中最多只能有i个元素,为了保证魔力更大,我们应该把堆中更小的多余出来的元素删除,也就是每次删除堆顶元素,直到堆中元素个数小于等于i,然后此时堆中剩余的元素就是我们要选择的i个元素,堆顶元素就是这一堆数中的最小值,然后判断一下更新答案即可。
时间复杂度:O(n),n表示数组长度,每个元素最多只会进堆出堆一次。
空间复杂度:O(n),需要一个优先队列来维护这个一过程。
cpp代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int T,n;
int v[N],p[N];
priority_queue<int,vector<int>,greater<int>>pq;
void solve()
{
while(!pq.empty())pq.pop(); //多测,记得清空全局优先队列
cin>>n;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=n;i++)cin>>p[i];
LL ans=0;
int x=0;
for(int i=n;i>0;i--)
{
pq.push(v[p[i]]);
while(pq.size()>i){ //删除多余的元素
pq.pop();
}
// 只有当删除的元素个数足够i-1,那么选择i个元素才是合法的,否则删除的元素不够,就不能选择i个元素
//在保证魔力最大的基础上选择更少的位置
if(pq.size()==i && (LL)pq.top()*i>=ans){
ans=(LL)pq.top()*i;
x=i;
}
}
cout<<ans<<' '<<x<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--)
{
solve();
}
return 0;
}