贪心|nssl 1477.赛
D e s c r i p t i o n Description Description 有 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
S o l u t i o n Solution Solution 设 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大的数的和的数据结构
【贪心|nssl 1477.赛】可以用离散化+【权值线段树/(二分+树状数组)】解决,也可以用对顶堆解决,这里只讲后者。
把集合 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)
C o d e Code Code
#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_queueq1,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()
推荐阅读
- (三)|(三) 贪心算法
- 刷题记录|【蓝桥必胜】试题 算法训练 kAc给糖果你吃-贪心排序
- #|算法设计与分析(Java实现)——贪心算法(集合覆盖案例)
- Centos|Centos 7 OpenSSL1.0.2K 动态库升级1.0.2U
- 贪心算法(2)(金条切割问题、点灯问题、IPO问题)
- Codeforces22|Codeforces22 D. Segments(贪心)
- 题库-CF|【Codeforces Round 263 (Div 2)C】【贪心 哈弗曼思维】Appleman and Toastman 每个非1size子树延展为2子树的最大权
- CodeForces-1282B2 K for the Price of One (Hard Version)(DP || 前缀和+贪心)
- 贪心算法刷题
- ZOJ-3447---Doraemon's Number Game (贪心+大数)