浅谈邻项交换法和反悔贪心
浅谈邻项交换法和反悔贪心
邻项交换法
对于 \(n\) 个多元组,需要按照顺序完成某种任务,且顺序可自行安排,就可通过邻项交换法确定最优顺序。
设元素为 \(a_i\),考虑 \(a_i\) 和 \(a_{i+1}\)。
\(a_i\) 和 \(a_{i+1}\) 需要调换位置当且仅当 \(f(\left\{a_{i+1},a_i\right\})<f(\left\{a_i,a_{i+1}\right\})\)。
其中 \(f\) 是估价函数,每道题都不一样,需要自行设计。
一般来说将上述式子化简完可以得到对于 \(n\) 元组内部的一种新的排序方式。
具体地到后面例题讲。
反悔贪心
有一类题不能保证按顺序取直到不能取的方法就是最优的,所以要在后面时考虑反悔。
大概是维护一个集合表示当前取了什么元素,以及一个最优值。
在遇到一个新元素 \(x\) 时,分两种情况:
- 能取。那就笑纳。
- 不能取,考虑已选取的集合内是否存在一个元素 \(y\),使得放弃 \(y\) 就能要 \(x\) 且最优值更优。如果是就放弃 \(y\),取 \(x\)。否则就什么都不做。
这样我们就可以在保证总体不劣的情况下使最优值尽可能优秀。
例题
考虑相邻的两场比赛 \(a_{i},a_{i+1}\),其中 \(a_i=\left\{x_i,l_i\right\}\),\(a_{i+1}\) 同理。
那么需要交换当且仅当原来是仅能参加 \(a_i\),不能参加 \(a_{i+1}\)。
化成式子就是这样:
令 \(S=\sum_{j=1}^{i-1} x_j\)。那么式子就变为:
然后交换后更优就是都能参加,式子:
发现若 \(S+x_{i+1}\le l_i\),必有 \(S\le l_i\),所以删掉 \(S\le l_i\)。
然后把所有式子的 \(S\) 单独拎到一边,有:
对于第二个式子,注意到:\(l_{i+1}-x_i<S\le \min\{l_{i+1},l_i-x_{i+1}\}\),发现 \(l_{i+1}-x_i\) 必定小于 \(l_{i+1}\),所以可以不考虑第二个式子。
注意到剩下 \(l_{i+1}-x_i<l_i-x_{i+1}\),移项,有 \(l_{i+1}+x_{i+1}<l_i+x_i\)。
所以只要按照 \(l_i+x_i\) 从小到大排序即可。
然后注意我们只确定了选择的顺序,没有确定选哪些,所以要用到反悔贪心。
维护当前选了什么和选了几个。
那么遇到不能选的比赛肯定是尽可能使当前等级降低并参加。
那么降低等级就是返回之前选过 \(x_i\) 最大的比赛。
这可以用大根堆维护。
于是就做完了,时间复杂度 \(O(n\log n)\)。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
const int N=5e5+5;
int n;
ljl cur;
priority_queue<ljl> pq;
struct NODE{
ljl x,l;
bool operator < (const NODE a)const{
return x+l<a.x+a.l;
}
}node[N];
int main(){
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>node[i].x;
for(int i=1;i<=n;++i)cin>>node[i].l;
sort(node+1,node+n+1);
for(int i=1;i<=n;++i)
{
if(cur<=node[i].l)
{
cur+=node[i].x;
pq.push(node[i].x);
}
else
{
if(cur-pq.top()<=node[i].l&&node[i].x<pq.top())
{
//注意上面判断其实只需要node[i].x<pq.top()就行了,第一个条件可以推式子知道是一定满足的
cur-=pq.top();
pq.pop();pq.push(node[i].x);
cur+=node[i].x;
}
}
}cout<<(int)pq.size()<<'\n';
return 0;
}

浙公网安备 33010602011771号