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

下面介绍三种解法:

  1. 直接对着上面的贪心进行魔改

    上面的贪心错误的原因是因为没有权衡好选择两星还是选择一星。
    我们可以再建立一个优先队列保存当前零星的关卡中,\(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;
    }
    
  2. 反悔贪心

    考虑每次增加一颗星星,取最小的增加方式。
    按照最朴素的贪心有两种增加方式:
    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;
    }
    
  3. 数据结构

    我们将这 \(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;
    }
    
posted @ 2020-11-22 11:44  tzc_wk  阅读(175)  评论(1)    收藏  举报