大一ACM暑假集训第二周学习总结

Week_2

Day_1

求最大公约数时使用GCD

int gcd(int a,int b)
{
    if(b==0)return a;
    return gcd(b,a%b);
}

当遇到高次幂相关问题时考虑快速幂

/*计算a的b次方*/
long long binpow(long long a,long long b)
{
    long long res=1;
    while(b>0)
    {
        if(b & 1)
            res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}

素数筛法——线筛

/*筛选出2-MAXN的素数*/
void init()
{
	phi[1]=1;
	for(int i=2;i<MAXN;++i)
    {
        if(!vis[i])
        {
            phi[i]=i-1;
            pri[cnt++]=i;
        }
        for(int j=0;j<cnt;++j)
        {
            if(i*pri[j]>=MAXN)
                break;
            vis[i*pri[j]]=1;
            if(i%pri[j])
            {
                phi[i*pri[j]]=phi[i]*(pri[j]-1);
            }
            else{
                phi[i+pri[j]]=phi*pri[j];
                break;
            }
        }
    }
}

Day_2

前缀和

常出现于数列问题中

如求1 2 3 4 5 6 7数列中第2个-第6个的和

循环做法为2+3+4+5+6=20[i=l;i<r;i++]

前缀和进行预处理,将输入数组依次求和变为

1 2 6 10 15 21 28

则2-6的和为 21-1=20**[r-(l-1)]**

位运算

  1. & 与,两个都是1才为1
  2. | 或,有一个1就是1
  3. ^ 异或 不同时为1
  4. ~取反 0变1,1变0
  5. num << i 二进制表示左移i位
  6. num>> i 二进制表示右移i位

获取a的第b位:

int getBit(int a,int b)
{
    return(a>>b)&1;
}

字符串哈希

将字符串变成一个p进制的数字

p=131或13331

string x;
p[0]=1;
h[0]=0;
for(int i=0;i<n;i++)
{
	p[i+1]=p[i]*p;
    h[i+1]=h[i]*p+x[i];
}

Day_3

二叉树

#include <bits/stdc++.h>
using namespace std;
typedef struct Node
{
    int date;
    struct Node *l_son;
    struct Node *r_son;
    Node():date(0),l_son(NULL),r_son(NULL){}
}node;

/// 新建节点
node* newNode(int val)
{
    node* no = new node();
    no->date = val;
    return no;
}
void insert(node *root ,int x)
{
    if( root == NULL )
    {
    root = newNode(x);
    return ;
	}
    ///建树规则
    if( ... )
    {
        insert(root -> l_son , x );
    }
    else
    {
        insert(root -> r_son , x );
    }
}

/// 更新、查找
void search(node* root,int x,int newdate)
{
    if( root == NULL)
    {
    	return ; /// 递归边界
	}
    if( root -> date == x)
    {
        root -> date = newdate;
        return ;
	}
    search( root -> l_son , x , newdate );
    search( root -> r_son , x , newdate );
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(0);
    ...
    return 0;
}

树的遍历

层序遍历

void LayerOrder(node* root)
{
    queue<node*> q;
    q.push( root );
    while( !q.empty() )
    {
        node* no =q.front();
        q.pop();
        cout << no -> date << endl;
        if( now -> l_son != NULL )
        {
        	q.push( no -> l_son );
        }
        else
        {
        	q.push( no -> r_son );
        }
    }
}

先序遍历

void preOrder(node* root)
{
    if(root == NULL)
    {
        return ;
    }
    cout << root->date<<endl;
    preOrder(root -> l_ron);
    preOrder(root -> r_son);
}

中序遍历

void inOrder(node* root)
{
    if( root == NULL )
    {
        return ;
    }
    inOrder( root -> l_son );
    cout << no -> daye << endl;
    inOrder( root -> r_son );
}

后序遍历

void postOrder(node* root)
{
    if( root == NULL )
    {
        return ;
    }
    postOrder( root -> l_son );
    postOrder( root -> r_son );
    cout << no -> daye << endl;
}

Day_4

并查集

并查集是一种树形结构,它用一棵树来表示一个集合,不同的集合其实就构成了一个
森林。
根据不同的需要添加格外的数组

//常用属性
p[i]//代表i的父节点
size[i]//代表i这个集合中有多少点
dis[i]//代表i到父节点的距离

初始化集合

for (int i = 1; i <= n; i ++ ){
p[i] = i;
size[i]=1;
dis[i]=0;
}

寻找祖宗节点

int find(int x)
{
	if (p[x] != x)
    {
        int u = find(p[x]);//路径压缩
        dis[x] += dis[p[x]];
        p[x] = u;
    }
	return p[x];
}

合并集合(将a合并在b集合上)

void merge(int a,int b)
{
    int pa=find(a);
    int pb=find(b);
    size[pb] += size[pa];
    p[pa] = pb;
    dis[pa] = distance;//根据具体问题描述偏移量
}

带权并查集(边的权值——用一个数组记录每个节点和其父节点的权值)

Day_5

线段树

区间类的问题经常使用

单点修改、单点查询、区间修改、
区间查询(区间求和,求区间最大值,求区间最小值)等

const int N = 1e5 + 5;
strcut Node
{
    int l,r;
    int sum;
}Tree[N>>2];

建树(push_up根据题意定)

void push_up(int rt)
{
    Tree[rt].sum = Tree[rt << 1].sum + Tree[rt << 1|1].sum
}

void build(int rt,int l,int r)
{
    Tree[rt].l = l , Tree[rt].r = r;
    if( l == r )
    { /// 递归到了叶结点
        Tree[rt].sum = A[r]; /// A为输入数组
        return ;
    }
    int mid = l + r >> 1;
    build( rt<<1 , l , mid );
    build( rt<<1|1, mid + 1 , r );
    push_up( rt );
}

单点更新

void update(int rt,int pos,int val)
{
/*如果找到*/
if( Tree[rt].l == Tree[rt].r )
    { /// 如果到了pos这个位置就更新
    A[pos] +=val;
    Tree[rt].sum +=val;
    return ; /// 一定要return
    }
/*寻找点*/
    int mid = ( Tree[rt].l + Tree[rt].r ) >> 1;
    if( pos <= mid ) update( rt << 1,pos,val );
    /// 注意思考什么时候是if-else,什么时候是if-if
    else update( rt << 1|1, pos, val );
    push_up(rt); /// 更新父结点
}

单点查询

int update(int rt,int pos)
{
if( Tree[rt].l == Tree[rt].r )
    { /// 如果到了pos这个位置就更新
    	return Tree[rt].sum; /// 一定要return
    }
int mid = ( Tree[rt].l + Tree[rt].r ) >> 1;
int res = 0;
if( pos <= mid ) res = update( rt << 1,pos ); 
    /// 注意思考什么时候是if-else,什么时候是if-if
else res = update( rt << 1|1, pos );
return res;
}

区间查询

int update(int rt,int l,int r)
{
if( l <= Tree[rt].l && r >= Tree[rt].r )
    { /// 如果到了pos这个位置就更新
    	return Tree[rt].sum; /// 一定要return
    }
int mid = ( Tree[rt].l + Tree[rt].r ) >> 1;
int res = 0;
if( l <= mid ) res += update( rt << 1,l,r ); 
    /// 注意思考什么时候是if-else,什么时候是if-if
if( r > mid ) res += update( rt << 1|1, l,r );
return res;
}

lazy标记(push_down)

void push_down(int rt)
{
/// 如果lazy标记非空,我们就把标记下传
if( Tree[rt].lazy != 0 )
    {
    int mid = (Tree[rt].l + Tree[rt].r) >> 1;
    /// 更新子节点
    Tree[rt << 1].sum += Tree[rt].lazy * ( mid - Tree[rt].l + 1); /// 注意这后面
    的值
    Tree[rt << 1 | 1].sum += Tree[rt].lazy * ( Tree[rt].r - mid) ; /// 注意这后面
    的值
    /// 继续把标记下传
    Tree[rt << 1].lazy += Tree[rt].lazy;
    Tree[rt << 1|1].lazy += Treert].lazy;
    /// 清空当前节点的lazy标记
    Tree[rt].lazy = 0;
    }
}

带标记下传的区间查询

ll rangeQuery(int rt,int l ,int r)
{
    if( l <= Tree[rt].l && r >= Tree[rt].r )
    {
        return Tree[rt].sum;
    }
    ll mid = ( Tree[rt].l + Tree[rt].r ) >> 1;
    push_down(rt); /// 标记下传
    ll son = 0;
    if( l <= mid ) son += rangeQuery(rt << 1,l,r); /// 查询左儿子
    if( r > mid ) son += rangeQuery(rt << 1|1,l,r); /// 查询右儿子
    push_up(rt);
    return son;
}

带lazy标记的区间修改

void rangeUpdate(int rt, int l,int r,int val)
{
    if( l <= Tree[rt].l && r >= Tree[rt].r )
    {
    Tree[rt].sum += val * (Tree[rt].r - Tree[rt].l + 1); /// 把所有的区间中的值都先修改
    Tree[rt].lazy += val;
    return ;
    }
    int mid = ( Tree[rt].l + Tree[rt].r ) >> 1;
    push_down(rt); /// 标记下传
    if( l <= mid ) rangeUpdate( rt << 1,l,r,val );
    if( r > mid ) rangeUpdate( rt << 1|1,l,r,val );
    push_up(rt); /// 区间值上传
}

本周感想

算法原理懂了但是在代码实现的时候很不顺畅于是越来越依靠板子,因为对算法实现所采用的每一步的过程,比如先做什么后做什么,用什么样的数据结构去做等等不太熟悉,其次由于之前没有接触这些方法,之前的思维一直都是暴力模拟,导致自己看到题的有些时候总想用最简单的if-else和多层循环等简单方法暴力模拟出来,而并不会去想这道题应该是哪一个部分的知识点,应该用那部分的算法知识去做等等,我觉得这是我目前的两个需要找到解决方法的地方。其实仔细想到就是因为对算法只是初步了解了,但并没有进行代码上的深层理解,如果做到此,应该能大幅缓解目前的问题所在。在后面的学习里考虑仔细想一下各个算法操作的每一行的代码实现是怎样的,将其过程在大脑中模拟出来。还有就是要注意那些题应该用哪方面的算法去求解,比如快速幂的题目就绝大多都和幂相关,遇到题目上的这些关键信息就要考虑使用这些算法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

打豆豆1234

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值