一、区间问题
区间选点、最大不相交区间数量
题解:
1)如何选点?
(1)将每个区间按右端点递增排序、
(2)从前往后遍历每个区间,如果当前区间中已经包含点,跳过,否则选取当前区间的右端点
PS:此方法选择的点必然是序列中无交集的区间的数量,所以此方法适用于两题
2)证明?
最优解 a n s ans ans:所有合法方案中的最小值,贪心解 c n t cnt cnt:上述选法
(1)上述选法必然会使每个区间里至少包含一个点,是一种合法方案,所以 a n s < = c n t ans<=cnt ans<=cnt
(2)通过贪心方案我们知道序列中包含了 c n t cnt cnt个相互之间没有交集的区间,所以对于一个合法方案,必然至少包含 c n t cnt cnt个点,所以 c n t < = a n s cnt<=ans cnt<=ans
#include<bits/stdc++.h>
#define r first
#define l second
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
PII g[N];
int n;
int main(){
cin>>n;
for(int i=0;i<n;i++){
int a,b; cin>>a>>b;
g[i]={b,a};
}
sort(g,g+n);
int ans=1,pos=g[0].r;
for(int i=0;i<n;i++){
if(g[i].l>pos){
pos=g[i].r;
ans++;
}
}
cout<<ans;
}
按照左端点递增排序,选左端点做法:
#include<bits/stdc++.h>
#define l first
#define r second
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
PII g[N];
int n;
int main(){
cin>>n;
for(int i=0;i<n;i++){
int a,b; cin>>a>>b;
g[i]={a,b};
}
sort(g,g+n);
int ans=1,pos=g[n-1].l;
for(int i=n-1;i>=0;i--){
if(g[i].r<pos){
pos=g[i].l;
ans++;
}
}
cout<<ans;
}
区间分组
题解:
1)方案?
(1)将所有区间按左端点递增排序
(2)从左往右枚举每个区间,判断能否放进现有的组中,能则更新选择组的 m a x r maxr maxr,否则新开一个组
2)证明?
最优解 a n s ans ans,贪心解 c n t cnt cnt
(1)对于贪心解 c n t cnt cnt来说,一定可以使每个区间被包含到某个组中,所以 a n s < = c n t ans<=cnt ans<=cnt
(2)对于贪心解 c n t cnt cnt,每一组所包含的区间一定有交集,否则不会新开一个组,所以对于一个合法方案至少包含 c n t cnt cnt个组,所以 c n t < = a n s cnt<=ans cnt<=ans
左端点递增排序
#include<bits/stdc++.h>
#define l first
#define r second
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int n;
PII g[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int a,b; cin>>a>>b;
g[i]={a,b};
}
sort(g,g+n);
priority_queue<int,vector<int>,greater<int>>q;
q.push(g[0].r);
for(int i=1;i<n;i++){
auto [l,r]=g[i];
int t=q.top();
if(t<l){
q.pop(),q.push(r);
}
else q.push(r);
}
cout<<q.size();
}
右端点递增排序
#include<bits/stdc++.h>
#define r first
#define l second
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int n;
PII g[N];
int main(){
cin>>n;
for(int i=0;i<n;i++){
int a,b; cin>>a>>b;
g[i]={b,a};
}
sort(g,g+n);
priority_queue<int>q;
q.push(g[n-1].l);
for(int i=n-2;i>=0;i--){
auto [r,l]=g[i];
int t=q.top();
if(t>r){
q.pop(),q.push(l);
}
else q.push(l);
}
cout<<q.size();
}
巨巨的做法:(扫描线)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int g[N*2];
int main(){
cin>>n;
int cnt=0;
for(int i=0;i<n;i++){
int l,r; cin>>l>>r;
g[cnt++]=l*2,g[cnt++]=r*2+1;
}
sort(g,g+cnt);
int ans=0,now=0;
for(int i=0;i<cnt;i++){
if(g[i]%2) now--;
else now++;
ans=max(ans,now);
}
cout<<ans;
}
区间覆盖
题解:
1)方案?
(1)将所有区间按左端点递增排序
(2)从左往右遍历每个区间,在所有能覆盖 s t st st的区间中,选择右端点最大的区间,然后将st更新右端点的最大值
2)证明?(反证法)
最优解 a n s ans ans,贪心解 c n t cnt cnt
不考虑无解情况
假设 a n s ans ans与 c n t cnt cnt在第 i i i个区间不同,由于贪心解找的是能覆盖st的情况下右端点最大的区间,所以当前 a n s r < c n t r ans_r<cnt_r ansr<cntr,对于最优解的第 i + 1 i+1 i+1区间 x x x有 a n s r > = x l ans_r>=x_l ansr>=xl,所以必然有 c n t r > x l cnt_r>x_l cntr>xl,所以最优解在第 i i i个位置即使替换为与贪心解相同的区间也不会影响选取的区间个数,即使之后仍有与贪心解中不同的区间,也可将最优解中的区间替换为对应的贪心解中的区间,所以 a n s = c n t ans=cnt ans=cnt
#include<bits/stdc++.h>
#define l first
#define r second
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
int n,st,ed;
PII g[N];
int main(){
cin>>st>>ed>>n;
for(int i=0;i<n;i++){
int a,b; cin>>a>>b;
g[i]={a,b};
}
sort(g,g+n);
int ans=0;
bool f=0;
for(int i=0;i<n;i++){
int j=i,pos=-2e9;
while(j<n&&g[j].l<=st) pos=max(g[j].r,pos),j++;//找最靠右的端点
if(pos<st){
ans=-1;
break;
}
ans++;
if(pos>=ed){
f=1;
break;
}
st=pos;
i=j-1;
}
if(!f) ans=-1;
cout<<ans;
}
二、排序不等式
排队打水
题解:
如何算每个人的等待时间?
对于第 i i i个人而言,它后面的人必然要等它打完水,所以对于第 i i i个人,所有人花费在它身上的等待时间为 s [ i ] ∗ ( n − i ) s[i]*(n-i) s[i]∗(n−i),所以应该按照每个人打水时间递增的顺序打水才能让总的打水时间最短
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n;
int s[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i];
sort(s+1,s+n+1);
ll ans=0;
for(int i=1;i<n;i++) ans+=s[i]*(n-i);
cout<<ans;
return 0;
}
三、中位数定理
货仓选址
题解:
找中位数
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int main(){
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int sum=0;
for(int i=0;i<n;i++) sum+=abs(a[i]-a[n/2]);//当n为偶数时取左中右中均可
cout<<sum;
}
四、推公式
耍杂技的牛
题解:
如何构造最小的最大风险值?
局部调整法
第
i
i
i头牛,第
i
+
1
i+1
i+1头牛的风险值为:
第
i
头
牛
:
a
n
s
i
=
w
1
+
w
2
+
.
.
.
+
w
i
−
1
−
s
i
第
i
+
1
头
牛
:
a
n
s
i
+
1
=
w
1
+
w
2
+
.
.
.
+
w
i
−
1
+
w
i
−
s
i
+
1
第i头牛:ans_i=w_1+w_2+...+w_{i-1}-s_i\\ 第i+1头牛:ans_{i+1}=w_1+w_2+...+w_{i-1}+w_i-s_{i+1}
第i头牛:ansi=w1+w2+...+wi−1−si第i+1头牛:ansi+1=w1+w2+...+wi−1+wi−si+1
交换这两头牛在序列中的顺序:
第
i
头
牛
:
a
n
s
i
=
w
1
+
w
2
+
.
.
.
+
w
i
−
1
+
w
i
+
1
−
s
i
第
i
+
1
头
牛
:
a
n
s
i
+
1
=
w
1
+
w
2
+
.
.
.
+
w
i
−
1
−
s
i
+
1
第i头牛:ans_i=w_1+w_2+...+w_{i-1}+w_{i+1}-s_i\\ 第i+1头牛:ans_{i+1}=w_1+w_2+...+w_{i-1}-s_{i+1}
第i头牛:ansi=w1+w2+...+wi−1+wi+1−si第i+1头牛:ansi+1=w1+w2+...+wi−1−si+1
设
C
=
∑
j
=
1
i
−
1
w
j
C=\sum_{j=1}^{i-1}{w_j}
C=∑j=1i−1wj
得:
变化 | 第i个位置上的牛 | 第i+1个位置上的牛 |
---|---|---|
交换前 | C − s i C-s_i C−si | C + w i − s i + 1 C+w_i-s_{i+1} C+wi−si+1 |
交换后 | C + w i + 1 − s i C+w_{i+1}-s_i C+wi+1−si | C − s i + 1 C-s_{i+1} C−si+1 |
化简得
变化 | 第i个位置上的牛 | 第i+1个位置上的牛 |
---|---|---|
交换前 | − s i -s_i −si | w i − s i + 1 w_i-s_{i+1} wi−si+1 |
交换后 | w i + 1 − s i w_{i+1}-s_i wi+1−si | − s i + 1 -s_{i+1} −si+1 |
什么情况下交换前的 m a x > max> max> 交换后的 m a x max max?
1)显然 w i − s i + 1 > − s i + 1 w_i-s_{i+1}>-s_{i+1} wi−si+1>−si+1
2)当 w i + s i > w i + 1 + s i + 1 w_i+s_i>w_{i+1}+s_{i+1} wi+si>wi+1+si+1时,有 w i − s i + 1 > w i + 1 − s i w_i-s_{i+1}>w_{i+1}-s_i wi−si+1>wi+1−si
所以当 w i + s i > w i + 1 + s i + 1 w_i+s_i>w_{i+1}+s_{i+1} wi+si>wi+1+si+1时,一定有交换前的风险最大值= m a x ( − s i , w i − s i + 1 ) > max(-s_i,w_{i}-s_{i+1})> max(−si,wi−si+1)> 交换后的风险最大值
所以当按照 w + s w+s w+s递增顺序排列时,会取得最小的风险最大值
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N=1e5+10;
PII g[N];
int n;
int main(){
cin>>n;
for(int i=0;i<n;i++){
int a,b;
cin>>a>>b;
g[i]={a+b,b};
}
sort(g,g+n);
int ans=-2e9-10,res=0;
for(int i=0;i<n;i++){
auto [w,s]=g[i];
ans=max(ans,res-s);
res+=w-s;
}
cout<<ans;
}