内容
- STL
- 二分
- \(0/1\) 分数规划
STL
都有一定的常数,谨慎使用
#include<algorithm>
区间基本上都是左开右闭的。
- \(sort\) 的本质是重载小于号。 \(sort(a,a+n,cmp)\)
nth_element
-
作用是,调用完上述函数后,没有完整进行排序,但是 \(a[x]\) 之前的元素都比 \(a[x]\) 小,\(a[x]\) 后面的元素都比 \(a[x]\) 大。也就是求出了第x 小元素,\(a[x]\)。(单次复杂度\(O(n)\) )
-
\(nth\_element(a+1,a+1+n,a+x,cmp)\)
unique 与离散化
- 把一个(有序)数组去重,返回不重复的结尾的迭代器的下一位置。
- unique 也可以自定义cmp 函数,\(unique(a+1,a+n+1,cmp)\) 这样调用。cmp(A,B): 需要在A==B 时返回 true,其他情况 false。(比较少用)
- 只能把相邻的相同的元素进行去重,因此需要是有序的。
- 如果返回不重复的结尾,可以再调用后减一(或减去 \(a+1\))
离散化
- 进行映射(只考虑大小关系的情况下),进行数值压缩
- 需要辅助空间的空间开销
//对a数组进行离散化
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5+10;
int a[maxn],n;
int c[maxn],tot;//辅助空间
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",a+i),c[i]=a[i];
sort(c+1,c+1+n);
tot=unique(c+1,c+1+n)-(c+1);
for(int i=1;i<=n;i++)a[i]=lower_bound(c+1,c+1+tot,a[i])-c;
for(int i=1;i<=n;i++)printf("%d ",a[i]);
return 0;
}
vector
- 下标从 \(0\) 开始,到 \(size()-1\) 结束
#include <vector>
vector<int> A;
vector<int> B(A);//A复制一份
vector<int> B(n);//开n个0
vector<int> B(n,i);//开n个i
-
\(A.front()\) 与 \(A.back()\) 分别指的是开头和结尾的元素。
-
\(A.insert(it,x,n)\) ,在 it 迭代器后插入一(n)个元素,复杂度和数组暴力插入一样。
-
\(A.erase(it)\); \(A.erase(it1,it2)\):擦除 \(it1\) 到 \(it2\) 之间的这些元素。
测试代码:
#include <iostream> #include <cstdio> #include <vector> #include <algorithm> using namespace std; vector<int> A; vector<int> :: iterator it; int main(){ for(int i=10;i>=1;i--)A.push_back(i*10); sort(A.begin(),A.end()); for(it=A.begin();it!=A.end();it++){ printf("%d ",*it); } puts(""); for(int i=0;i<10;i++)printf("%d ",A[i]); puts(""); printf("size: %d\nempty: %d\nfront: %d\nback: %d",A.size(),A.empty(),A.front(),A.back()); }
set
- 本质是一个平衡树,常用来维护前驱后继
- \(s.empty()\),判断是否为空。
- \(s.size()\),返回 s 的大小。
- \(s.insert(x)\),插入元素 x 。
- \(s.erase(x) \ or \ s.erase(it)\), 把值为 x 的元素或 it 迭代器所指元素删除
- \(s.clear()\) ,清空集合
- \(it=s.find(x)\),返回 \(x\) 元素的迭代器,如果不存在,返回 end( )
set 的二分
- \(s.lower\_bound(x)\)
- \(s.upper\_bound(x)\)
set 结构体的代码实现(重载小于号)
#include <iostream>
#include <set>
#include <cstdio>
using namespace std;
struct node{
int x,y;
bool operator <(node a){
if(x!=a.x)return x<a.x;
return y<a.y;
}
};
bool operator <(node a,node b){
if(a.x!=b.x)return a.x<b.x;
return a.y<b.y;
}
set<node> s;
int main(){
for(int i=1;i<=10;i++){
node t;
t.x=i/2;t.y=i;
s.insert(t);
}
for(set<node>::iterator it=s.begin();it!=s.end();it++)cerr<<it->x<<" "<<it->y<<endl;
printf("%d\n",(int)s.size());
}
multiset
- 操作类似于 set ,可以用来维护可重复的前驱后继。
- \(multiset\ erase\) 一个元素时会把相等的元素都删掉。
删除一个相等的元素:
it=A.find();
A.erase(it);//通过删除迭代器来实现
queue
- \(q.front()\) 返回队首元素
- \(q.back()\) 返回队尾元素
- \(q.clear()\),清空,\(O(n)\)
- 无 \(iterator\) 操作
priority_queue
- 是一个二叉堆
- 通过加 \(greater\) 来编程小过程
- 结构体需要定义 \(operator \ <\),定义过程比较特殊:他会认为大的更小,小的更大
deque
- 双端队列
- 可以 用\([\ ]\) 访问
- 需要 \(include<deque>\)
- 较 \(vector\) 多了 \(push\_front()\) 和 \(pop\_front()\)
pair
- \(make\_pair\) 来得到一个 \(pair\)
- \(x.first\) 与 \(x.second\)
map
-
红黑树,是平衡树的一种
-
相当于一个 \(set<type(a),type(b)>\),重载了 \([\ \ ]\) 运算符
-
如果不存在 \(first=a\) 的元素,便会新建一个。否则会修改这个元素
常见的 STL 用法
- \(sort\)
- \(set\) 去重
- \(bfs\) 运用 \(queue\)
- \(sort + unique + lower\_bound\) 进行离散化
- \(vector\) 边长数组
- \(map\) 作劣质哈希 (复杂度多一个 \(log\))
- \(priority\_queue\) 跑最短路
二分
-
把求最优性问题转化为可行性问题
-
通常指二分答案,是一种难题里的工具
-
二分的题目答案必须有单调性
例题
P1182 数列分段 Section II
枚举和的最大值(答案),看看能不能符合题意。
#include <iostream>
#include <cstdio>
using namespace std;
int n,m,a[100005],l,r,mid,ans;
inline bool check(int x)
{
int tot=0,num=0;
for(int i=1;i<=n;i++)
{
if(tot+a[i]<=x)tot+=a[i];
else tot=a[i],num++;
}
num++;
//printf("x:%d num:%d\n",x,num);
return num>m;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),l=max(l,a[i]),r+=a[i];
while(l<r)
{
mid=l+r>>1;
if(check(mid))l=mid+1;
else r=mid;
}
cout<<mid;
return 0;
}
细节
- 注意二分写法与 check 函数的对应关系
P1083 [NOIP2012 提高组] 借教室
差分+二分
- 差分用来处理区间修改,利用其与前缀和的相反性质推得每一天的 \(need\)
- 二分来二分答案(因为具有单调性)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define LL long long
const int maxn = 1e6 + 6;
LL rest[maxn],dff[maxn],l[maxn],r[maxn],need[maxn],d[maxn];
int n,m;
void clear(){memset(dff,0,sizeof dff);return ;}
bool check(int x){
clear();
for(int i=1;i<=x;i++){
dff[l[i]]+=d[i];
dff[r[i]+1]-=d[i];
}
for(int i=1;i<=n;i++){
need[i]=need[i-1]+dff[i];
if(need[i]>rest[i])return false;
}
return true;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",rest+i);
for(int i=1;i<=m;i++)scanf("%lld%lld%lld",d+i,l+i,r+i);
if(check(m)){
puts("0");return 0;
}
int L=1,R=m,mid;
while(L<R){
mid=L+R>>1;
if(check(mid))L=mid+1;
else R=mid;
}
printf("-1\n%d",L);
return 0;
}
后记
- 可以通过差分来修改原序列并得到原序列
CF1360H Binary Median
通过确定删除 \(n\) 个数后中位数的位置,来求得中位数
二分适用
- 看上去很难直接找到最优解
- 题目答案具有单调性
- 可以判断二分出来的 \(mid\) 的合法性
二分的本质是加速枚举答案,每次把答案区间除以二
0/1 分数规划
- 每一个元素有两个属性 \(a_i \ b_i\)
- 选择一些元素使得 \(\sum a_i/\sum b_i\) 达到最大或最小
- \(e.g.\) 最优比例生成树
做法
- 通过二分实现,二分一个 \(mid\)
回头看最优比例生成树,我们就可以给每一条边赋值求一棵最大生成树
例题
P4377 [USACO18OPEN]Talent Show G
使得 \(\sum(ti-mid*wi)\geq0\) 并且 \(\sum\ wi\geq W\)
\(0/1\) 分数规划和背包问题(\(wi\)虽然很大但是 \(W\) 小)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 260 , maxw = 1000 + 10;
const int INF = 0x3f3f3f3f;
const double eps=1e-5;
int n,W;
int w[maxn],t[maxn];
double c[maxn],f[maxw];
bool check(double x){
for(int i=1;i<=n;i++)c[i]=(double)(t[i]-x*w[i]);//c作为转移的辅助空间
for(int i=1;i<=W;i++)f[i]=-INF;
for(int i=1;i<=n;i++){//0/1背包
for(int j=W+w[i];j>=w[i];j--){
if(j>=W)f[W]=max(f[W],f[j-w[i]]+c[i]);
else f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
return f[W]>=0;
}
int main(){
scanf("%d%d",&n,&W);
double l=0.0,r=0.0;
for(int i=1;i<=n;i++)scanf("%d%d",w+i,t+i),r+=t[i];
while(l+eps<=r){
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%d",(int)(l*1000));
return 0;
}
后记
- 一般 \(0/1\) 分数规划会结合背包来考察
- 需要用到实数二分和 \(double\)