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;
}
Codeforces 436E - Cardboard Box(贪心/反悔贪心/数据结构)的更多相关文章
- Codeforces 436E Cardboard Box (看题解)
Cardboard Box 贪了个半天贪不对, 我发现我根本就不会贪心. 我们先按b排序, 然后枚举选两颗心的b的最大值, 在这个之前的肯定都要选一个, 因为前面的要是一个都没选的话, 你可以把当前选 ...
- Codeforces 437C The Child and Toy(贪心)
题目连接:Codeforces 437C The Child and Toy 贪心,每条绳子都是须要割断的,那就先割断最大值相应的那部分周围的绳子. #include <iostream> ...
- Codeforces Round #546 (Div. 2) D 贪心 + 思维
https://codeforces.com/contest/1136/problem/D 贪心 + 思维 题意 你面前有一个队列,加上你有n个人(n<=3e5),有m(m<=个交换法则, ...
- [CSP-S模拟测试]:trade(反悔贪心)
题目传送门(内部题62) 输入格式 第一行有一个整数$n$.第二行有$N$个整数:$a_1\ a_2\ a_3\cdot\cdot\cdot a_n$. 输出格式 一行一个整数表示最大收益. 样例 样 ...
- 洛谷 P5470 - [NOI2019] 序列(反悔贪心)
洛谷题面传送门 好几天没写题解了,写篇题解意思一下(大雾 考虑反悔贪心,首先我们考虑取出 \(a,b\) 序列中最大的 \(k\) 个数,但这样并不一定满足交集 \(\ge L\) 的限制,因此我们需 ...
- 题解-CF436E Cardboard Box
题面 CF436E Cardboard Box \(n\) 个关卡,对每个关卡可以花 \(a_i\) 时间得到 \(1\) 颗星,或花 \(b_i\) 时间得到 \(2\) 颗星,或不玩.问获得 \( ...
- Codeforces Round #228 (Div. 2) C. Fox and Box Accumulation(贪心)
题目:http://codeforces.com/contest/389/problem/C 题意:给n个箱子,给n个箱子所能承受的重量,每个箱子的重量为1: 很简单的贪心,比赛的时候没想出来.... ...
- CF436E Cardboard Box(贪心)
题意 有nnn个关卡,第iii关可以花费aia_iai的代价打一颗星,bib_ibi的代价打两颗星.保证1≤ai<bi≤1091\le a_i<b_i\le10^91≤ai<b ...
- Codeforces Round #570 (Div. 3) G. Candy Box (hard version) (贪心,优先队列)
题意:你有\(n\)个礼物,礼物有自己的种类,你想将它们按种类打包送人,但是打包的礼物数量必须不同(数量,与种类无关),同时,有些礼物你想自己留着,\(0\)表示你不想送人,问你在送出的礼物数量最大的 ...
随机推荐
- Golang通脉之基础入门
为什么要学 Go 性能优越感:Go 极其地快,其性能与 Java 或 C++相似.在使用中,Go 一般比 Python 要快 30 倍: 序列化/去序列化.排序和聚合中表现优异: 开发者效率较高:多种 ...
- pycharm安装第三方库
https://jingyan.baidu.com/article/4853e1e54b845e1909f7268f.html
- mall笔记
介绍 SpringBoot.SpringCloud.SpringCloudAlibaba.Nacos.Sentinel.Seata整合demo. 软件架构 JDK 1.8 Spring Boot 2. ...
- DDD领域驱动设计-概述-Ⅰ
如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.) ...
- [火星补锅] 水题大战Vol.2 T1 && luogu P1904 天际线 题解 (线段树)
前言: 当时考场上并没有想出来...后来也是看了题解才明白 解析: 大家(除了我)都知道,奇点和偶点会成对出现,而出现的前提就是建筑的高度突然发生变化.(这个性质挺重要的,我之前没看出来) 所以就可以 ...
- 2021.8.4考试总结[NOIP模拟30]
T1 毛衣衬 将合法子集分为两个和相等的集合. 暴力枚举每个元素是否被选,放在哪种集合,复杂度$O(3^n)$.考虑$\textit{meet in the middle}$. 将全集等分分为两部分分 ...
- 设计的MOS管三极管简单开关电路驱动能力不够3
16楼说得非常明白,补充一点,R3如果不要,会有下冲产生.4 Q: Z/ G G1 s8 Z- } 能解释下为什么会产生过冲吗?9 i, P* D* X) u. t/ b ^ 让我们这些菜鸟学习学 ...
- 算法:拉丁方阵(Latin Square)
拉丁方阵(英语:Latin square)是一种 n × n 的方阵,在这种 n × n 的方阵里,恰有 n 种不同的元素,每一种不同的元素在同一行或同一列里只出现一次.以下是两个拉丁方阵举例: 拉丁 ...
- centos7 使用iptables
关闭selinux,不关闭时,iptables不读取配置文件 重启生效 centos7 中默认的防火墙是firewalld,使用iptables需要先关闭firewalld防火墙,安装iptables ...
- Jenkins 邮件发送
1.jenkins新建任务 2.配置svn 3.maven项目构建配置pom.xml 使用maven命令 clean test 构建前清除: 4.系统管理 => 插件管理 =>可选安装邮件 ...