反悔贪心
最初的贪心的策略是每次选最大的可行的数
如果我们按照这个贪心策略,得到的答案为 100 + 1 100+1 100+1 然而实际答案是 99 + 99 99+99 99+99
我们在不改变贪心的策略的情况下,考虑反悔(即撤销之前的操作)
例如此图,我们在选完 100 100 100 的情况下,撤销 100 100 100 的操作,改选 99 + 99 99+99 99+99 (肯定不会只选一个 99 99 99)
对于反悔操作,我们可以通过构造一个新的结点等于其两边的结点减去当前的结点
例如此图,在我们选取 a 1 a1 a1 后, a n s = 100 ans=100 ans=100 ,将 a 1 a1 a1 改成 99 + 99 − 100 = 98 99+99-100=98 99+99−100=98 ,再在当前可挑选点中挑选最大的点即 a 1 ′ = 98 a_1^{'}=98 a1′=98 , a n s ans ans 变为 198 198 198
对于找最大值的操作,我们使用堆,而对于标记一个点左右的点我们需要使用链表
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN],pre[MAXN],nxt[MAXN],vis[MAXN];
struct Node{
int id,val;
bool operator < (const Node &x)const
{
return x.val>val;
}
};
priority_queue <Node> q;
void init()
{
for(int i=1;i<=n;i++)
pre[i]=i-1, nxt[i]=i+1;
pre[1]=n,nxt[n]=1;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
q.push((Node){i,a[i]});
}
if((n>>1)<m) {puts("Error!"); return 0;}
init();
int res=0;
while(m--)
{
while(vis[q.top().id]) q.pop();
Node hd=q.top(); q.pop();
res+=hd.val; vis[pre[hd.id]]=vis[nxt[hd.id]]=1;
a[hd.id]=a[pre[hd.id]]+a[nxt[hd.id]]-a[hd.id];
q.push((Node){hd.id,a[hd.id]});
pre[hd.id]=pre[pre[hd.id]], nxt[hd.id]=nxt[nxt[hd.id]];
nxt[pre[hd.id]]=hd.id, pre[nxt[hd.id]]=hd.id;
}
cout<<res;
return 0;
}
-
- 根据题意可得,如果办公楼 i i i 已经有电线连接,那么 i i i 不会再和其他办公楼相连
-
- 如下图可知,电线肯定连接相邻的两座办公楼
- 如下图可知,电线肯定连接相邻的两座办公楼
例如此图,问题可以转化为求 在 1 − 2 1-2 1−2、 2 − 3 2-3 2−3 、 3 − 4 3-4 3−4、 4 − 5 4-5 4−5 号办公楼之间不相邻的线段之和最小,经典的反悔贪心问题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
const int INF=1e9+5;
int n,k,a[MAXN],w[MAXN],vis[MAXN],pre[MAXN],nxt[MAXN];
struct Node{
int id,val;
bool operator < (const Node &x)const{
return x.val<val;
}
};
priority_queue <Node> q;
void init()
{
w[0]=w[n]=INF;
for(int i=1;i<=n;i++)
pre[i]=i-1,nxt[i]=i+1;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
w[i-1]=a[i]-a[i-1];
}
init();
for(int i=1;i<n;i++) q.push((Node){i,w[i]});
ll res=0;
while(k--)
{
while(vis[q.top().id]) q.pop();
Node hd=q.top(); q.pop();
res+=hd.val;
vis[pre[hd.id]]=vis[nxt[hd.id]]=1;
w[hd.id]=w[pre[hd.id]]+w[nxt[hd.id]]-w[hd.id];
q.push((Node){hd.id,w[hd.id]});
pre[hd.id]=pre[pre[hd.id]], nxt[hd.id]=nxt[nxt[hd.id]];
nxt[pre[hd.id]]=hd.id,pre[nxt[hd.id]]=hd.id;
}
cout<<res;
return 0;
}
反悔贪心例题 CF730I Olympiad in Programming and Sports
题意: n n n 个人,每个人编程能力为 a i a_i ai 运动能力为 b i b_i bi 选 p p p 个人去编程,一个人只能去一个项目,选 s s s 个人去运动,问最大能力和
我们首先确定编程队的 p p p 个人,然后将其余的人定义为 闲散人员
在确定编程队之后,运动队当前缺 s s s 个人,对于加入运动队的人员,选择两种情况的最优情况
-
- 从闲散人员中挑一个人 i i i 去运动队,贡献为 + b i +b_i +bi
-
- 从编程队挑一个人 i i i 去运动队,并挑一个闲散人员 k k k 去编程队 , 贡献为 + b i − a i + a k +b_i-a_i+a_k +bi−ai+ak
那么我们用三个堆,分别维护 闲散人员的 a a a ,闲散人员的 b b b ,以及编程队人员的 b − a b-a b−a (只有已经选入编程队的人才有可能反悔)
同时,我们用一个 b e l o n g belong belong 数组表记录
- 0 0 0 闲散人员
- 1 1 1 编程队
- 2 2 2 运动队
注意:每次反悔操作将状态改变后要重新改变对应不同状态的堆,如:编程队员变为运动队员后进入运动队员的堆,退出变成队员的堆
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e3+5;
struct Node{
int id,val;
bool operator <(const Node &x)const{
return x.val>val;
}
}a[MAXN],b[MAXN],A[MAXN];
int n,p,s,belong[MAXN],ans;
bool cmp(Node x,Node y)
{
return x.val>y.val;
}
priority_queue <Node> q1; // 闲散人员的 a
priority_queue <Node> q2; // 闲散人员的 b
priority_queue <Node> q3; // 编程队人员的 b-a
int main()
{
cin>>n>>p>>s;
for(int i=1;i<=n;i++)
cin>>a[i].val,a[i].id=i,A[i]=a[i];
for(int i=1;i<=n;i++)
cin>>b[i].val,b[i].id=i;
sort(A+1,A+n+1,cmp);
for(int i=1;i<=p;i++)
{
int cur=A[i].id;
ans+=A[i].val;
belong[cur]=1;
q3.push((Node){cur,b[cur].val-a[cur].val});
}
for(int i=1;i<=n;i++)
if(belong[i]==0)
q1.push(a[i]),q2.push(b[i]);
while(s--)
{
while(!q1.empty() && belong[q1.top().id]!=0) q1.pop();
while(!q2.empty() && belong[q2.top().id]!=0) q2.pop();
while(!q3.empty() && belong[q3.top().id]!=1) q3.pop();
if(q2.top().val>q3.top().val+q1.top().val)
{
ans+=q2.top().val;
belong[q2.top().id]=2;
q2.pop();
}
else
{
ans+=q3.top().val+q1.top().val;
belong[q3.top().id]=2;
belong[q1.top().id]=1;
q3.pop();
q3.push((Node){q1.top().id, b[q1.top().id].val-q1.top().val});
q1.pop();
}
}
cout<<ans<<endl;
for(int i=1;i<=n;i++)
if(belong[i]==1)
cout<<i<<" ";
puts("");
for(int i=1;i<=n;i++)
if(belong[i]==2)
cout<<i<<" ";
return 0;
}
题意:n 个关卡,对每个关卡,你可以花 a i a_i ai 打到一颗星,也可以花 b i b_i bi 代价得到两颗星,也可以不玩。问获得 w w w 颗星的最小代价
对于得到一颗星,有以下四种情况
我们需要找每次操作的最小值,用 5 个堆维护
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3e5+5;
struct Node{
int id,val;
bool operator < (const Node &x) const{
return x.val<val;
}
};
int n,w,a[MAXN],b[MAXN],star[MAXN],ans;
priority_queue <Node> q1; // ai 0 to 1
priority_queue <Node> q2; // bi 0 to 2
priority_queue <Node> q3; // -ai 1 to 0
priority_queue <Node> q4; // ai-bi 2 to 1
priority_queue <Node> q5; // bi-ai 1 to 2
signed main()
{
cin>>n>>w;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
q1.push((Node){i,a[i]});
q2.push((Node){i,b[i]});
}
while(w--)
{
while(!q1.empty() && star[q1.top().id]!=0) q1.pop();
while(!q2.empty() && star[q2.top().id]!=0) q2.pop();
while(!q3.empty() && star[q3.top().id]!=1) q3.pop();
while(!q4.empty() && star[q4.top().id]!=2) q4.pop();
while(!q5.empty() && star[q5.top().id]!=1) q5.pop();
int res=1e9+7,opt=0;
if(!q1.empty() && res>q1.top().val) res=q1.top().val,opt=1;
if(!q5.empty() && res>q5.top().val) res=q5.top().val,opt=2;
if(!q4.empty() && !q2.empty() && res>q4.top().val+q2.top().val) res=q4.top().val+q2.top().val,opt=3;
if(!q3.empty() && !q2.empty() && res>q3.top().val+q2.top().val) res=q3.top().val+q2.top().val,opt=4;
ans+=res;
if(opt==1) // 0 to 1
{
star[q1.top().id]=1;
q5.push((Node){q1.top().id, b[q1.top().id]-a[q1.top().id]});
q3.push((Node){q1.top().id, -a[q1.top().id]});
q1.pop();
}
if(opt==2)// 1 to 2
{
star[q5.top().id]=2;
q4.push((Node){q5.top().id, a[q5.top().id]-b[q5.top().id]});
q5.pop();
}
if(opt==3)// 2 to 1 & 0 to 2
{
star[q4.top().id]=1, star[q2.top().id]=2;
q5.push((Node){q4.top().id, b[q4.top().id]-a[q4.top().id]});
q3.push((Node){q4.top().id, -a[q4.top().id]});
q4.pop();
q4.push((Node){q2.top().id, a[q2.top().id]-b[q2.top().id]});
q2.pop();
}
if(opt==4)// 1 to 0 & 0 to 2
{
star[q3.top().id]=0, star[q2.top().id]=2;
q4.push((Node){q2.top().id, a[q2.top().id]-b[q2.top().id]});
q1.push((Node){q3.top().id, a[q3.top().id]});
q2.pop();
q2.push((Node){q3.top().id, b[q3.top().id]});
q3.pop();
}
}
cout<<ans<<endl;
for(int i=1;i<=n;i++) cout<<star[i];
return 0;
}