有 n n n个物品,第 i i i个物品代价为 v i v_i vi
有两个人 A , B A,B A,B,它们各自喜欢 a , b a,b a,b个物品,分别是 a i , b i a_i,b_i ai,bi
要求从中选出 m m m个物品,满足至少有 k k k个 A A A喜欢的,至少有 k k k个 B B B喜欢的
求最小代价和
数据范围: n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105
设 G G G表示两人共同喜欢的物品构成的集合
用 A , B A,B A,B分别表示只有 A / B A/B A/B喜欢物品的集合(代码中用了 o n l y only only表示)
将 C C C表示 A , B A,B A,B都不喜欢的集合,显然 G , A , B , C G,A,B,C G,A,B,C从小到大取是更优的
枚举一个数 i i i,表示从 G G G集合中选取的个数
接着从 A A A集合中,选出 k − i k-i k−i个数
从 B B B集合中,选出 k − i k-i k−i个数
那么我们此时还需要选 z = m − ( k − i ) − ( k − i ) − i = m − 2 k + i z=m-(k-i)-(k-i)-i=m-2k+i z=m−(k−i)−(k−i)−i=m−2k+i个数
那么这些数都包括哪些呢?
包括集合 C C C以及集合 A , B A,B A,B中没有选取的
那么我们把集合 A , B A,B A,B中没有选取的,都当做是集合 C C C中的元素
现在,我们就要在集合 C C C中选出 z z z个最小的数,补齐数量即可
也就是说,我们需要写一种支持每次加入一个数,查询前 z z z大的数的和的数据结构
可以用离散化+【权值线段树/(二分+树状数组)】解决,也可以用对顶堆解决,这里只讲后者。
把集合 C C C放入一个小根堆中(这里的集合 C C C包括 A , B A,B A,B中没有选取的),记为 q 1 q_1 q1
前面的选取,我们直接找到 A , B A,B A,B都不喜欢的放入,中间由于 i i i我们是从小到大枚举的,所以 k − i k-i k−i在慢慢变小,从 A , B A,B A,B中选择的元素会越来越少,踢出来的会越来越多,但是每次最多只会各多一个
我们只需要把踢出的这些元素放入 q 1 q_1 q1即可
后面补齐 z z z的部分,我们建立一个大根堆 q 2 q_2 q2,满足其堆顶小于 q 1 q_1 q1的堆顶(也就是 q 2 q_2 q2中最大的比 q 1 q_1 q1中最小的还小)如果控制 q 2 q_2 q2的大小为 z z z(即长度不够时,从 q 1 q_1 q1中拿元素去补),那么它里面元素的和就是这一部分的答案
最后总结一遍答案的计算过程
s u m sum sum:选取 A , B A,B A,B共同喜欢元素的和,直接求和即可
s u m a suma suma:只有 A A A喜欢元素的和,直接计算,每当踢出一个元素,要减去
s u m b sumb sumb:和上面同理
s u m 0 sum0 sum0:表示我们已经选取了 i i i个共同的,各取了 k − i k-i k−i个两人喜欢的,剩下 z z z个选取的和(即 q 2 q_2 q2中元素的和),如果 q 2 q_2 q2的元素不足 k k k个,则从 q 1 q_1 q1中选取元素放入 q 2 q_2 q2中直到 q 2 q_2 q2恰好有 z z z个元素,此时这 z z z个元素就是原 q 1 q_1 q1中的前 z z z小值,同时,记得维护 q 1 q_1 q1的堆顶小于 q 2 q_2 q2的堆顶,否则交换它们的堆顶直到合法为止,这个过程也要维护 s u m 0 sum0 sum0
最终答案 A n s = m i n { s u m + s u m a + s u m b + s u m 0 } Ans=min\{sum+suma+sumb+sum0\} Ans=min{sum+suma+sumb+sum0}
时间复杂度:由于每个物品入堆出堆的次数是 n n n级别的,所以总的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
#include
#include
#include
#include
#define LL long long
using namespace std;int n,m,k,a,b,x,z;
LL sum0,suma,sumb,sum,ans=1e18,gt[200010],onlya[200010],onlyb[200010],v[200010];
bool rea[200010],reb[200010];
priority_queue<LL>q1,q2;
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
signed main()
{
n=read();m=read();k=read();
for(register int i=1;i<=n;i++) v[i]=read();
a=read();for(register int i=1;i<=a;i++) x=read(),rea[x]=true;
b=read();for(register int i=1;i<=b;i++) x=read(),reb[x]=true;
for(register int i=1;i<=n;i++)
if(rea[i]&&reb[i]) gt[++gt[0]]=v[i];
else if(rea[i]) onlya[++onlya[0]]=v[i],suma+=v[i];//维护suma
else if(reb[i]) onlyb[++onlyb[0]]=v[i],sumb+=v[i];//维护sumb
else q1.push(-v[i]);//两个都不喜欢的,放入待选集合
sort(gt+1,gt+1+gt[0]);
sort(onlya+1,onlya+1+onlya[0]);
sort(onlyb+1,onlyb+1+onlyb[0]);
for(register int i=0;i<=min(gt[0],(long long)k);i++)
{
if(i>0) sum+=gt[i];
while(onlya[0]>k-i)
{
suma-=onlya[onlya[0]];
q1.push(-onlya[onlya[0]]);//A不选取的,放入待选集合
onlya[0]--;
}
while(onlyb[0]>k-i)
{
sumb-=onlyb[onlyb[0]];
q1.push(-onlyb[onlyb[0]]);//B不选取的,放入待选集合
onlyb[0]--;
}
z=m-onlya[0]-onlyb[0]-i;
if(z<0) continue;//不需要补齐(说明此时选的数量过了,是不合法的,必须要恰好m个元素)
if(onlya[0]!=k-i||onlyb[0]!=k-i) continue;//如果物品没有选够,也是不合法的
while(q1.size()&&(q2.size()<z))//补齐q2集合至z个元素
{
sum0-=q1.top();
q2.push(-q1.top());
q1.pop();
}
while(q1.size()&&q2.size()&&-q1.top()<q2.top())//维护q2中所有元素都比q1小
{
LL p=-q1.top(),q=q2.top();
sum0+=p-q;
q1.pop();q2.pop();
q1.push(-q);q2.push(p);
}
if(q2.size()==z) ans=min(ans,sum+suma+sumb+sum0);//统计答案
}
if(ans==1e18) printf("-1");else
printf("%lld",ans);
}