\(\#A\) \([JZOJ5461]Shopping\)
有\(N\)个物品和\(K\)张优惠券,给出每个物品的正常价格\(P_i(P_i\in[0,10^9])\)和使用优惠券后的价格\(Q_i\),求出最多花费\(M\)元钱可以买到的最多物品数。
\(N\in [1,5\times 10^4]\),\(K\in [1,N]\),\(P_i\in[0,10^9]\),\(Q_i\in [0,P_i]\),\(M\in [0,10^{14}]\)
首先,如果优惠券数多于物品数,所有物品一定都是用优惠券,直接按\(Q\)排序贪心即可;
如果优惠券数少于物品数,则优惠券一定全部使用。按\(Q\)排序,先选取前\(K\)个物品,使用优惠券。
考虑之后的物品只有不用优惠券和使用前面物品反悔用过的优惠券两种方式,维护三个小根堆\(q_1,q_2,q_3\),分别维护已使用优惠券的物品反悔代价\(P-Q\),未购买物品的\(P\)值,未购买物品的\(Q\)值。
每次从\(q_2,q_3\)中各取堆顶尝试更新答案,\(q_2\)更新答案的方式为取出\(q_1\)中的最优代价反悔并以\(Q\)值买当前商品,\(q_3\)更新答案的方式为直接在原来答案上累加当前物品的\(P\)值。
每次取两个答案中更优秀的更新答案至全部为空或超出价值限制为止,注意若\(q_2\)更新答案同时需要维护\(q_1\)。
#include<cmath>
#include<queue>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50010
#define R register
#define gc getchar
#define inf 9000000000000000000
using namespace std;
typedef long long ll;
bool use[N];
struct sub{ll v1,v2;}s[N];
priority_queue<pair<ll,ll> > q1,q2,q3;
inline bool cmp(sub a,sub b){return a.v2<b.v2;}
inline ll rd(){
ll x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
int main(){
ll n=rd(),k=rd(),v=rd(),sum=0,ans=0;
for(R ll i=1;i<=n;++i){s[i].v1=rd(); s[i].v2=rd();}
sort(s+1,s+1+n,cmp);
if(k>=n){
for(R ll i=1;i<=n&&sum<=v;++i) ++ans,sum+=s[i].v2;
printf("%lld\n",ans); return 0;
}
for(R ll i=1;i<=k;++i){
if(sum+s[i].v2>v){printf("%lld\n",ans);return 0;}
++ans; sum+=s[i].v2; use[i]=1;
q1.push(make_pair(s[i].v2-s[i].v1,i));
}
if(sum==v){printf("%lld\n",ans);return 0;}
for(R ll i=min(ans+1,k+1);i<=n;++i){
q2.push(make_pair(-s[i].v2,i));
q3.push(make_pair(-s[i].v1,i));
}
while((!q2.empty())||(!q3.empty())){
while((!q2.empty())&&use[q2.top().second]) q2.pop();
while((!q3.empty())&&use[q3.top().second]) q3.pop();
ll sum1=inf,sum2=inf;
if(!q2.empty()) sum2=sum-q1.top().first-q2.top().first;
if(!q3.empty()) sum1=sum-q3.top().first;
if(min(sum1,sum2)<=v){
++ans;
if(sum1<sum2){sum=sum1; use[q3.top().second]=1;q3.pop();}
else{
q1.push(make_pair(s[q2.top().second].v2-s[q2.top().second].v1,q2.top().second));
sum=sum2; use[q2.top().second]=1; q2.pop();
}
}
else break;
}
printf("%lld\n",ans);
return 0;
}
\(\#B\) \([JZOJ5455]Tree\)
给出一棵\(N\)个节点的树,求覆盖树上任意\(K\)个点最少需要的边数。
\(N\in [1,10^5]\),\(K\in [1,N]\)
对于每条选择的边,覆盖了两个不重复端点的边是更优秀的,其余的贡献只能是\(1\),所以树形\(DP\)求出树边的最大独立集。
状态\(f[U][0/1]\)表示当前处理节点\(U\)的子树,节点\(U\)没被边覆盖\(/\)被边覆盖时,子树内合法选择最多的边数。
对于\(0\)状态,其最优解显然为子树所有\(1\)状态答案之和。
对于\(1\)状态,其最优解对每一个子树讨论,转移为\(f[u][1]=max\{f[u][0]-f[v][1]+f[v][0]\}+1\),注意应将完整的\(0\)状态答案求出后再更新\(1\)状态;
若求出答案可覆盖点数超过\(K\),则所有边都为“较优秀”的边,只需保留\(\frac{k+1}{2}\)条边即可。
若求出答案不够覆盖\(K\)个点,则答案为\(ans+K-ans\times 2\),即最大独立集以外选中的边贡献均为\(1\)。
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100000
#define R register
#define gc getchar
using namespace std;
int n,k,tot,hd[N],f[N][2];
struct edge{int to,nxt;}e[N<<1];
inline void add(int u,int v){
e[++tot].to=v; e[tot].nxt=hd[u]; hd[u]=tot;
}
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
void dfs(int u,int fa){
for(R int i=hd[u],v;i;i=e[i].nxt)
if((v=e[i].to)!=fa){dfs(v,u); f[u][0]+=f[v][1];}
for(R int i=hd[u],v;i;i=e[i].nxt)
if((v=e[i].to)!=fa) f[u][1]=max(f[u][1],f[u][0]-f[v][1]+f[v][0]+1);
}
inline void work(){
n=rd(); k=rd(); tot=0;
memset(f,0,sizeof(f));
memset(hd,0,sizeof(hd));
for(R int i=2,x;i<=n;++i){
add(i,(x=rd())); add(x,i);
}
dfs(1,-1);
int ans=max(f[1][1],f[1][0]);
if(ans*2>=k) printf("%d\n",(k+1)>>1);
else printf("%d\n",k-ans);
}
int main(){
int t=rd();
while(t--) work();
return 0;
}