知识点总结
二分,三分,二进制枚举,堆栈,优先队列,线段树(没学完,下周总结),拓扑排序,二分图的最大匹配,匈牙利算法,动态规划,Floyd算法 ,dfs算法,还有一些巧妙的数学知识解决实际问题。
二分
提要:用于求函数的最值问题,做一些常见问题的优化,例如优化暴力枚举。
二分函数
在从小到大的排序数组中,
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
在其他的STL容器中也有类似的二分函数
set(multiset)
lower_bound(key_value) ,返回第一个大于等于key_value的定位器,不存在返回end;
upper_bound(key_value),返回最后一个大于等于key_value的定位器,不存在返回end;
经典例题
K-位数差_2021秋季算法入门班第三章习题:二分、三分、01(重现赛)@IR101 (nowcoder.com)
思路:首先,这是一个非常经典的二分题目,还插入了分治的思想,一看就会,最后附一段二分模版
#include<bits/stdc++.h>
#define int long long
using namespace std;
int L,n,m,k;
int a[1000010];
int s[1000010];
int jz[]={10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
int asolve(int l,int r){
if(l==r) return 0;
int mid=(l+r)/2;
int res=asolve(l,mid)+asolve(mid+1,r);
sort(a+mid+1,a+r+1);
for(int i=l;i<=mid;i++){
for(int j=0;j<9;j++){
if(jz[j]>a[i]) res+=a+r+1- lower_bound(a+mid+1,a+r+1,jz[j]-a[i]);
}
}
return res;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cout<<asolve(1,n);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
// cin>>t;
while(t--){
solve();
}
}
// while(l<=r){
// mid=(r+l)/2;
// if(check(mid)) r=mid-1;
// else l=mid+1;
// }
三分
提要
三分题目的特点,就是结果分布图类似于二次函数,可以通过限制搜索次数来搜索整个区间,搜索过程中记录最小值或最大值则为答案。在搜完之后,还可以在[l,r]区间中检验一下结果。
经典例题1
b.Linear Approximation - AtCoder arc100_a - Virtual Judge
#include<bits/stdc++.h>
#define FIO ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef long double ld;
void test_case()
{
ll n;
cin>>n;
ll a[n];
ll mx=0;
for(int i=0;i<n;i++)cin>>a[i],mx=max(mx,a[i]);
ll low=-1e12,high=1e12;
ll ans=4e18;
for(int i=0;i<=300;i++)//由于pow(2,300)大于了2e12,所以必定能把这两个区间搜完
{
ll m1=low+(high-low)/3;
ll m2=high-(high-low)/3;
ll fm1=0;
ll fm2=0;
for(int j=0;j<n;j++)fm1+=abs(a[j]-(m1+j+1));
for(int j=0;j<n;j++)fm2+=abs(a[j]-(m2+j+1));
ans=min(ans,fm1);
ans=min(ans,fm2);//过程中记录了最小值
if(fm1<fm2)high=m2-1;
else low=m1+1;
}
cout<<ans;
}
int main()
{
FIO
int t;
t=1;
// cin>>t;
while(t--)
{
test_case();
}
}
经典例题2
Q-[SCOI2010]传送带_2021秋季算法入门班第三章习题:二分、三分、01(重现赛)@IR101 (nowcoder.com)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int L,n,m,k;
double k1,k2;
double P,Q,R;
struct Point
{
double x,y;
Point(){};
Point(double xx, double yy):x(xx),y(yy){};
Point operator / (double a){return Point(x/a,y/a);}
Point operator - (const Point &a){return Point(x-a.x,y-a.y);}
Point operator + (const Point &a){return Point(x+a.x,y+a.y);}
}A,B,C,D;
double dist(Point a,Point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double calc(Point x){
Point l=C,r=D,midl,midr,y;
double ans1,ans2;
while(abs(dist(l,r))>1e-6){
y=(r-l)/3;
midl=l+y;
midr=r-y;
ans1=dist(midl,D)/Q+dist(x,midl)/R;
ans2=dist(midr,D)/Q+dist(x,midr)/R;
if(ans2>ans1) r=midr;
else l=midl;
}
return dist(x,l)/R+dist(l,D)/Q;
}
void solve(){
cin>>A.x>>A.y>>B.x>>B.y>>C.x>>C.y>>D.x>>D.y>>P>>Q>>R;
Point l=A,r=B,midl,midr,x;
double ans1,ans2;
while(abs(dist(l,r))>1e-6)
{
x=(r-l)/3;
midl=l+x;
midr=r-x;
ans1=calc(midl)+dist(midl,A)/P;
ans2=calc(midr)+dist(midr,A)/P;
if(ans2>ans1) r=midr;
else l=midl;
}
printf("%.2f",calc(l)+dist(A,l)/P);
}
signed main(){
int t=1;
//cin>>t;
while(t--){
solve();
}
}
堆栈,队列,优先队列
提要
根据题意判断是否需要使用,栈先入后出,队列先入先出,优先队列自动排序有大根堆和小根堆。在这里再次区分一下STL中set/map 与 priority_queue 中greater、less 的用法区别
set和map:底层都是红黑树 less<> 最小堆,greater<>是最大堆。 默认是less。
make_heap: less<>() 展现出来的是最大堆, greater<>()展现出来是最小堆。 默认是less。
priority_queue: 底层是使用heap实现的,所以表现出来的特性和heap一致。
less<>() 展现出来的是最大堆, greater<>()展现出来是最小堆。 默认是less。
比较常用,这里就不举例题了。
二进制枚举
提要
对任一数来说,所面临的问题是取或不取,在二进制中便可以用1或0来表示。由于每个数都会对应一串二进制数字,那么结果便有2n个状态,如1000001便相对于取0号位元素和最后一号元素,其他元素不取。那么我们可以枚举这2n个状态,通过遍历当前状态的每一位来判断,然后对取的元素求和判断是否为一个合格的状态。因为之前就已经使用过,使用起来还比较简单。
经典例题
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,k;
int a[210];
void solve() {
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int cnt=min((long long)9,n);
vector<int>p[210];
//二进制枚举,已经写过很多次了
for(int i=1;i<(1<<cnt);i++){
int ct=0;
vector<int>b;
for(int j=0;j<cnt;j++){
if((i>>j)&1) {
ct=(ct+a[j])%200;
b.push_back(j+1);
}
}
//如果之前已经搜到了余数相同的子序列,直接输出
if(p[ct].size()!=0){
cout<<"YES"<<endl;
cout<<p[ct].size();
for(int u=0;u<p[ct].size();u++){
cout<<" "<<p[ct][u];
}
cout<<endl;
cout<<b.size();
for(int u=0;u<b.size();u++){
cout<<" "<<b[u];
}
return ;
}
//如果没有搜到,将当前状态的数组放到p中
else {
p[ct]=b;
}
}
cout<<"NO"<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin>>t;
while (t--) {
solve();
}
return 0;
}
二部图的最大匹配,匈牙利算法
提要
离散数学的理论基础,应用到计算机编程上(匈牙利算法)
经典例题
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=110,M=2e5+10;
int n;
#define PII pair<int,int>
vector<PII>a;
vector<PII>b;
int c[220][220];//i,j,两点是否可以相连
int p[220];//当前搜索中,将已经搜到的所有点标记为1
int cnn[220];//表示每个点的对象是谁,初始对象为0,表示未连接
bool check(int x){
for (int j=1; j<=n; j++){
if (c[x][j]==1 && p[j]==0){
p[j]=1;//记录搜索状态
if (cnn[j]==0|| check(cnn[j])){//当前点未被连接或者可以转连其他点
// cout<<x<<" "<<j<<endl;
cnn[j]=x;
return true;
}
}
}
return false;
}
void solve()
{
cin>>n;
// memset(cnn,-1,sizeof cnn);
for(int i=1;i<=n;i++) {
int x,y;
cin>>x>>y;
a.push_back({x,y});
}
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
b.push_back({x,y});
}
for(int i=0;i<a.size();i++){
for(int j=0;j<b.size();j++){
if(b[j].first>a[i].first&&b[j].second>a[i].second){
c[i+1][j+1]=1;
}
}
}
int cnt=0;
for (int i=1; i<=n; i++)
{
memset(p,0,sizeof(p));
if(check(i)){
// cout<<i<<" "<<cnn[i]<<endl;
cnt++;//如果成立,答案加一
}
}
cout<<cnt<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin>>t;
while (t--) {
solve();
}
return 0;
}
Floyd算法
求多源汇最短路的算法,这里放一个模版,lei似的还有Dijkstra算法(单源最短路),spfa,bellman_ford(处理负权环)
Floyd求最短路
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main()
{
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while (Q -- )
{
int a, b;
scanf("%d%d", &a, &b);
int t = d[a][b];
if (t > INF / 2) puts("impossible");
else printf("%d\n", t);
}
return 0;
}
数学知识
1.二部图的最大匹配(省略)
2.抽屉原理(鸽洞原理)
第一抽屉原理:
原理1:
把多于n+1个的物体放到n个抽屉里,则至少有一个抽屉里的东西不少于两件。
原理2 :
把多于mn(m乘n)+1(n不为0)个的物体放到n个抽屉里,则至少有一个抽屉里有不少于(m+1)的物体。
原理3 :
把无穷多件物体放入n个抽屉,则至少有一个抽屉里 有无穷个物体。
第二抽屉原理:
把(mn-1)个物体放入n个抽屉中,其中必有一个抽屉中至多有(m—1)个物体(例如,将3×5-1=14个物体放入5个抽屉中,则必定有一个抽屉中的物体数少于等于3-1=2)。
eHappy Birthday! 2 - SMUOJ(也就是上面二进制枚举的例题)
3.图的构造
比赛过程中出现的问题
1.vector数组在初始化的时候预留了一些位置,push数据后再排序会出错
2.signed 没有return 0 导致TLE
3.三分过程当中l=midl-1,看起来仿佛是在确保范围,实际上最后一段区间永远都不会被访问到
4.判断条件时判断栈顶元素是否是某个值时必须要先判断栈是否为空
反思
首先本周前面几天状态还行,后面几天有点疲倦,整体状态还行,打字速度太慢。补题较慢,知识点总结不及时,题解不够详细,应该再记录一下做题过程当中遇到的问题。