暑假集训周报1

知识点总结

二分,三分,二进制枚举,堆栈,优先队列,线段树(没学完,下周总结),拓扑排序,二分图的最大匹配,匈牙利算法,动态规划,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个状态,通过遍历当前状态的每一位来判断,然后对取的元素求和判断是否为一个合格的状态。因为之前就已经使用过,使用起来还比较简单。

经典例题

 eHappy Birthday! 2 - SMUOJ

#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;
}

二部图的最大匹配,匈牙利算法

提要

离散数学的理论基础,应用到计算机编程上(匈牙利算法

经典例题

 b.AtCoder arc092_a


#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.图的构造

*e.Friendships - SMUOJ

详细题解

比赛过程中出现的问题

1.vector数组在初始化的时候预留了一些位置,push数据后再排序会出错

2.signed 没有return 0 导致TLE

3.三分过程当中l=midl-1,看起来仿佛是在确保范围,实际上最后一段区间永远都不会被访问到

4.判断条件时判断栈顶元素是否是某个值时必须要先判断栈是否为空

反思

        首先本周前面几天状态还行,后面几天有点疲倦,整体状态还行,打字速度太慢。补题较慢,知识点总结不及时,题解不够详细,应该再记录一下做题过程当中遇到的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值