Codeforces 436E - Cardboard Box(贪心/反悔贪心/数据结构)
题意:
有 \(n\) 个关卡,第 \(i\) 个关卡玩到 \(1\) 颗星需要花 \(a_i\) 的时间,玩到 \(2\) 颗星需要 \(b_i\) 的时间。(\(a_i<b_i\))
求玩到 \(w\) 颗星最少需要多少时间。
\(1 \leq n \leq 3 \times 10^5\)
yyq:“好题不常有,做过了就不能再错过了”
首先有一个显然的错误解法:初始将所有 \(a_i\) 压入优先队列,然后每次弹出优先队列中最小的值,如果它是一星的话就将 \(b_i-a_i\) 压入优先队列。
hack:
2 2
3 4
2 5
下面介绍三种解法:
-
直接对着上面的贪心进行魔改
上面的贪心错误的原因是因为没有权衡好选择两星还是选择一星。
我们可以再建立一个优先队列保存当前零星的关卡中,\(b_i\) 的最小值。
如果我们发现,当前零星的关卡中 \(b_i\) 的最小值 \(<\) 一星的最小的两个值之和。
那么我们就把 \(i\) 关卡玩到一星,并将 \(b_i-a_i\) 压入一星的队列当中。#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define fz(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++) #define fill0(a) memset(a,0,sizeof(a)) #define fill1(a) memset(a,-1,sizeof(a)) #define fillbig(a) memset(a,63,sizeof(a)) #define pb push_back #define mp make_pair typedef pair<int,int> pii; typedef long long ll; const int MAXN=3e5+5; int n,m,a[MAXN<<1],vis[MAXN<<1],star[MAXN]; priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > o,t; void clear(priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > &q){ while(!q.empty()&&vis[q.top().se]) q.pop(); } int main(){ scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&a[i+n]),a[i+n]-=a[i]; for(int i=1;i<=n;i++) o.push(mp(a[i],i)),t.push(mp(a[i]+a[i+n],i)); ll ans=0; while(m--){ clear(o);clear(t); int i=o.top().se;o.pop();clear(o); if(m&&t.size()&&a[i]+o.top().fi>=t.top().fi) o.push(mp(a[i],i)),i=t.top().se,t.pop(); if(i<=n) o.push(mp(a[i+n],i+n)); ans+=a[i];star[(i>n)?(i-n):i]++;vis[i]=true; } printf("%lld\n",ans); for(int i=1;i<=n;i++) printf("%d",star[i]); return 0; }
-
反悔贪心
考虑每次增加一颗星星,取最小的增加方式。
按照最朴素的贪心有两种增加方式:
I. 将一个零星的关卡 \(i\) 改为一星,代价为 \(a_i\)
II. 将一个一星的关卡 \(i\) 改为两星,代价为 \(b_i-a_i\)
我们利用返回贪心的思想,还有两种反悔方式:
III. 将一个一星关卡 \(i\) 改为零星关卡,并将一个零星关卡 \(j\) 改为两星关卡,代价为 \(b_j-a_i\)
IV. 将一个两星关卡 \(i\) 改为一星关卡,并将一个零星关卡 \(j\) 改为两星关卡,代价为 \(b_j-(b_i-a_i)\)考虑怎样维护这个最小值,我们建立五个小根堆:
\(q_1:\min\{a_i|star_i=0\}\)
\(q_2:\min\{b_i-a_i|star_i=1\}\)
\(q_3:\min\{b_i|star_i=0\}\)
\(q_4:\min\{-a_i|star_i=1\}\)
\(q_5:\min\{-b_i+a_i|star_i=2\}\)
对于方式 I,取出 \(q_1\) 的队首。
对于方式 II,取出 \(q_2\) 的队首。
对于方式 III,取出 \(q_3\) 和 \(q_4\) 的队首并将它们相加。
对于方式 III,取出 \(q_3\) 和 \(q_5\) 的队首并将它们相加。
还有个注意点:有些关卡星值的修改会牵扯到两个堆,我们要在每一次取状态时判断状态是否满足,不满足就 pop 掉。#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define fz(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++) #define fill0(a) memset(a,0,sizeof(a)) #define fill1(a) memset(a,-1,sizeof(a)) #define fillbig(a) memset(a,63,sizeof(a)) #define pb push_back #define mp make_pair typedef pair<int,int> pii; typedef long long ll; const ll INF=1e18; const int MAXN=3e5+5; int n,m,a[MAXN],b[MAXN],star[MAXN]; priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > q1,q2,q3,q4,q5; //q1: min{a[i]} star[i]=0 //q2: min{b[i]-a[i]} star[i]=1 //q3: min{b[i]} star[i]=0 //q4: min{-a[i]} star[i]=1 //q5: min{-b[i]+a[i]} star[i]=2 int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]); q1.push(mp(INF,n+1));q2.push(mp(INF,n+1));q3.push(mp(INF,n+1)); q4.push(mp(INF,n+1));q5.push(mp(INF,n+1));star[n+1]=-1; for(int i=1;i<=n;i++) q1.push(mp(a[i],i)),q3.push(mp(b[i],i)); ll ans=0; for(int i=1;i<=m;i++){ while(q1.size()>1&&star[q1.top().se]!=0) q1.pop(); while(q2.size()>1&&star[q2.top().se]!=1) q2.pop(); while(q3.size()>1&&star[q3.top().se]!=0) q3.pop(); while(q4.size()>1&&star[q4.top().se]!=1) q4.pop(); while(q5.size()>1&&star[q5.top().se]!=2) q5.pop(); ll x1=q1.top().fi,x2=q2.top().fi,x3=q3.top().fi+q4.top().fi,x4=q3.top().fi+q5.top().fi; ll mn=min(min(x1,x2),min(x3,x4));ans+=mn; // printf("%lld\n",mn); if(x1==mn){ int j=q1.top().se;star[j]=1;q1.pop(); q2.push(mp(b[j]-a[j],j));q4.push(mp(-a[j],j)); } else if(x2==mn){ int j=q2.top().se;star[j]=2;q2.pop(); q5.push(mp(-b[j]+a[j],j)); } else if(x3==mn){ int j1=q3.top().se,j2=q4.top().se; star[j1]=2;star[j2]=0;q3.pop();q4.pop(); q5.push(mp(-b[j1]+a[j1],j1)); q1.push(mp(a[j2],j2));q3.push(mp(b[j2],j2)); } else{ int j1=q3.top().se,j2=q5.top().se; star[j1]=2;star[j2]=1;q3.pop();q5.pop(); q5.push(mp(-b[j1]+a[j1],j1)); q2.push(mp(b[j2]-a[j2],j2));q4.push(mp(-a[j2],j2)); } } printf("%lld\n",ans); for(int i=1;i<=n;i++) printf("%d",star[i]); return 0; }
-
数据结构
我们将这 \(n\) 个关卡按 \(b_i\) 从小到大排序。
假设我们选择的两星的关卡中 \(b_i\) 最大的为关卡 \(l\),那么在关卡 \(l\) 前面的所有关卡都至少选择了一颗星(注意我们已经按 \(b_i\) 从小到大排好序了)。
为什么呢?很简单,如果有一个关卡 \(j\) 一颗星都没选,那么把 \(b_l\) 改为零星,\(b_j\) 改为两星肯定更优。
又因为关卡 \(l\) 是选择的两星的关卡中 \(b_i\) 最大的一个,所以在 \(l\) 后面的关卡至多选了一星。
维护 \(c_i=\begin{cases}b_i-a_i&(i\leq l)\\a_i&(i>l)\end{cases}\)
然后取 \(c_i\) 最小的 \(w-l\) 个就行了。
实现方式很多,我使用的是将 \(b_i-a_i\) 与 \(a_i\) 揉在一起离散化,然后线段树上二分。#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define fz(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++) #define fill0(a) memset(a,0,sizeof(a)) #define fill1(a) memset(a,-1,sizeof(a)) #define fillbig(a) memset(a,63,sizeof(a)) #define pb push_back #define mp make_pair typedef pair<int,int> pii; typedef long long ll; bool chkmin(ll &x,ll y){return ((x>y)?(x=y,1):0);} bool chkmax(ll &x,ll y){return ((x<y)?(x=y,1):0);} const int MAXN=3e5+5; int n,w,a[MAXN],b[MAXN],key[MAXN<<1],hs[MAXN<<1],cnt=0,num=0,ord[MAXN]; int get(int x){return lower_bound(hs+1,hs+num+1,x)-hs;} struct node{ int l,r,c;ll v; } s[MAXN<<3]; void build(int k,int l,int r){ s[k].l=l;s[k].r=r;if(l==r) return; int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r); } void modify(int k,int x,int addv,int addc){ if(s[k].l==s[k].r){s[k].c+=addc;s[k].v+=addv;return;} int mid=(s[k].l+s[k].r)>>1; if(x<=mid) modify(k<<1,x,addv,addc); else modify(k<<1|1,x,addv,addc); s[k].c=s[k<<1].c+s[k<<1|1].c; s[k].v=s[k<<1].v+s[k<<1|1].v; } ll query(int k,int c){ if(s[k].l==s[k].r) return 1ll*c*hs[s[k].l]; if(c>=s[k<<1].c) return s[k<<1].v+query(k<<1|1,c-s[k<<1].c); else return query(k<<1,c); } bool cmp(int x,int y){return b[x]<b[y];} int star[MAXN];pii p[MAXN]; int main(){ scanf("%d%d",&n,&w); for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]); for(int i=1;i<=n;i++) ord[i]=i;sort(ord+1,ord+n+1,cmp); // for(int i=1;i<=n;i++) printf("%d\n",ord[i]); for(int i=1;i<=n;i++) key[++cnt]=a[i],key[++cnt]=b[i]-a[i]; sort(key+1,key+cnt+1); for(int i=1;i<=cnt;i++) if(key[i]!=key[i-1]) hs[++num]=key[i]; build(1,1,num); for(int i=1;i<=n;i++) modify(1,get(a[i]),a[i],1); ll ans=1e18,sum=0;if(w<=n) ans=min(ans,query(1,w)); int pos=0; for(int i=1;i<=n;i++){ modify(1,get(a[ord[i]]),-a[ord[i]],-1); modify(1,get(b[ord[i]]-a[ord[i]]),b[ord[i]]-a[ord[i]],1); sum+=a[ord[i]];//printf("%lld\n",sum); // printf("%lld\n",query(1,w-i)); if(w-i<=n&&w-i>=0) if(chkmin(ans,query(1,w-i)+sum)) pos=i; } printf("%lld\n",ans); for(int i=1;i<=pos;i++) star[ord[i]]++; for(int i=1;i<=pos;i++) p[i]=mp(b[ord[i]]-a[ord[i]],ord[i]); for(int i=pos+1;i<=n;i++) p[i]=mp(a[ord[i]],ord[i]); sort(p+1,p+n+1);for(int i=1;i<=w-pos;i++) star[p[i].se]++; for(int i=1;i<=n;i++) printf("%d",star[i]); return 0; }