倍增笔记♂

听说前几天在某博文立了个flag,说要在北京补博客…果然不要乱立flag啊!【此处省去一万个flag】

  • RMQ问题(Range Minimum/Maximum Query)
    RMQ问题是指对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。【来自百度百科】
    一般情况下 RMQ问题可以用ST表解决(这里的一般情况是指没有修改操作,只进行查询)首先用dp进行预处理,通过进行合并两个区间的信息,来得知大的区间的信息。每次可以O(1)查询。
    我们设f[i][j]为从i开始连续的2^j次方个数中的最大值。f[i][0]则为从i开始的2^0次方个数,也就是从i开始1个数的最大值,也就是a[i]。这里要进行初始化。接下来是状态转移方程。我们把这个大区间分为两个区间(这个区间的长度肯定为偶数)。一段为i到i+2^(j-1)-1,一段为i+2^(j-1)到i+2^j-1(这两段的长度都为2^(j-1))。所以我们就有了这样的一个状态转移方程:f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1])。在查询的时候,我们设p等于以二为底r-l+1的对数,把区间分为两个区间完全覆盖。举例说明,要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2][2]和f[5][2]得到。
    有个图可以直观的感受一下:
    这里写图片描述

有个裸题是poj 3264 Balanced Lineup 链接

下面是代码(代码当中的k就是转移方程的i,i就是转移方程的j,j则是方便写代码的):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN=50000+10;
int f[MAXN][20],g[MAXN][20];
int a[MAXN];
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i][0]=a[i];
        g[i][0]=a[i];
    }
    for(int i=1,j=2;j<=n;i++,j*=2)
    {
        for(int k=1;k+j-1<=n;k++)
        {
            f[k][i]=max(f[k][i-1],f[k+(j>>1)][i-1]);
            g[k][i]=min(g[k][i-1],g[k+(j>>1)][i-1]);
        }
    }
    while(q--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        int p=int(log(r-l+1)/log(2));
        int maxx=max(f[l][p],f[r-(1<<p)+1][p]);
        int minn=min(g[l][p],g[r-(1<<p)+1][p]);
        printf("%d\n",maxx-minn);
    }
    return 0;
}
  • LCA(Lowest Common Ancestors) 最近公共祖先
    之前只会暴力的LCA,现在终于会了倍增版的。说句实话,打次的倍增不如暴力啊!有图为证!

这里写图片描述

【其实是自己太弱了】

其实倍增的LCA就是先对于树上的每个点用dfs序进行编号。然后在树上做好st表,但个人认为这个不如st表好理解QAQ。

我们设father[i][j]表示从i号点2^j倍的祖先,则father[i][0]即为节点i的父亲。
则father[i][j]=father[father[i][j-1]][j-1](从i跳到2^(j-1)倍,再从这个点网上跳2^(j-1)倍)
我最初学倍增的时候总会想会不会跳过去啊…其实后来发现,当它跳到它们的祖先,快要跳到祖先的时候,就会一步一步的跳,也就是father[i][0]。这样就会保证不会跳出去了。

poj 1330 Nearest Common Ancestors是个裸题【从名字就可以看出来233】链接

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=100000+50;
struct edge{
    int from,to;
}es[MAXN<<1];
int first[MAXN],next[MAXN],deep[MAXN],fa[MAXN];
int tot;
int father[MAXN][30];
void init()
{  
    memset(first,-1,sizeof(first));
    memset(deep,0,sizeof(deep));
    memset(fa,0,sizeof(fa));
    tot=0;
}
void build(int ff,int tt)
{
    es[++tot]=(edge){ff,tt};
    next[tot]=first[ff];
    first[ff]=tot;
}
void dfs(int u, int f)
{
    deep[u]=deep[f]+1;
    fa[u]=f;
    father[u][0]=f;
    for(int i=1;i<=24;i++)
        father[u][i]=father[father[u][i-1]][i-1];
    for(int i=first[u];i!=-1;i=next[i])
    {
        int v=es[i].to;
        if(v==f) continue;
        dfs(v,u);
    }
}
int lca(int x,int y)
{
    if(deep[x]<deep[y])
        swap(x,y);
    if(deep[x]>deep[y])
    {
        int t=deep[x]-deep[y];
        for(int i=24;i>=0;i--)
        {
            if(t&(1<<i))    //如果t的第i位=1 就跳这些步
                x=father[x][i];
        }
    } 
    if(x!=y)
    {
        for(int i=24;i>=0;i--)
        {
            if(father[x][i]!=father[y][i])
            {
                x=father[x][i];
                y=father[y][i];
            }
        }
    }
    else return x;
    return father[x][0];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,f,t;
        scanf("%d",&n);
        init();
        for(int i=1;i<=n-1;i++)
        {
            scanf("%d%d",&f,&t);
            build(f,t);
        }
        for(int i=1;i<=n;i++)
        {
            if(!deep[i])
                dfs(i,i);
        }
        int x,y;
        scanf("%d%d",&x,&y);
        cout<<lca(x,y)<<endl;   
    }
    return 0;
}

/*
1
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
12 7

4
*/
/*
5
1 2
2 3
2 4
4 5
*/
  • 快速幂
    非递归版的快速幂就是通过倍增实现的,我比较习惯打非递归版的,代码比较短而且给我一种很快的错觉233。直接上代码!(此代码基于codevs 1497)(此题数据比较水,代码比较naive,取膜取少了,注意要多取膜!)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
LL fast_pow(LL a,LL p,LL m)
{
    LL ans=1;
    for(;p;p>>=1,a=(a*a)%m)
        if(p&1)
            ans=(ans*a)%m;
    return ans;
}
int main()
{
    LL x,y,mod;
    scanf("%lld%lld%lld",&x,&y,&mod);
    cout<<x<<"^"<<y<<" "<<"mod"<<" "<<mod<<"=";
    printf("%lld",fast_pow(x,y,mod));
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值