关于RMQ问题的一些感悟

根据询问,修改,单点,区间,在线,离线的各种组合大概有8类问题吧。


一般就是4种解决办法吧。

1、线段树

2、树状数组

3、ST表

4、差分


线段树的话是最万能的方法了,无论询问,修改,单点,区间,在线,离线,都能做到O(logn)的时间复杂度,而且可以维护非常多种东西(区间和,最值等)。


树状数组是一种很轻便的工具,编码简单,常数小,缺点是只能求和。不同版本的树状数组能实现不同组合的单点,区间,询问,修改。时间复杂度都是O(logn)。


上述算法都是在线算法,既能处理在线问题,也能处理离线问题,然而处理离线问题有更优秀的离线算法。


ST表也是一种很轻便的工具,编码简单,常数小,缺点是只能求最值,而且是离线算法。预处理的时间复杂度是O(nlogn),但可以O(1)回答。


差分与其说是一种工具,不如说是一种小技巧。O(1)区间更新,O(n)离线,O(1)回答。一般用于解决区间覆盖等相关问题。

具体来讲就是假如区间[l,r]要加x。那就在a[l]+=x,a[r+1]-=x。离线就是求个前缀和,然后就可以O(1)回答单点,如果求两次前缀和,就能O(1)回答区间。

一般用于解决那种只需要最后要输出所有单点的答案的问题,比如hdu 1556 Color the ball。就完全没必要用线段树区间更新来解。

或者用于解决区间相关问题,比如一些滑动窗口之类的问题。


上述的RMQ都是一维线性的RMQ,事实上还有很多二维线性的RMQ问题。会用到一些上述工具的变种,比如:

1、二维线段树

2、二维树状数组

3、二维ST表

4、二维差分


其实二维差分就是一种动态规划啦。一般用于解决矩形内计数的问题。dp[i][j]表示前i行与前j列一共有多少个东西,然后你就可以O(1)回答每个矩形内有多少个东西啦。


上述RMQ都是线性的RMQ,事实上还有很多非线性的RMQ问题,比如树,图上的。


图上的不说了,我自己胡扯的,有没有也不知道,感觉更像是转化成其他问题来解决,大白P234那一题算不算呢?


就说树上的吧,其实依然是上述4种工具的变种啦。

1、树链剖分

2、。。。???。。。

3、LCA,非常像ST表,详见大白P345。

4、树上差分。


其实我写了那么多就是想写树上差分啦。

二维差分是二维的动态规划。

树上差分就是树形DP!!!(非常简单的那种dp)。

比如想让某条链[x,y](不妨设x的深度<y的深度,那么必须要满足x到y的路径上的点深度递增,换句话说就是要一直往下走,不能往上。)上的点都加上z。我们可以让dp[fa[x]]-=z,dp[x]+=z。

显然如果不是一直往下的链一定可以拆成两条一直往下的链,然后分别更新即可,需要用到LCA。

最后O(n)离线就是树形DP。dp[u]=∑dp[v](v是u的子节点)。

然后dp[u]就是单点的值啦。


codeforces 739B


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 200010;

ll n;
ll a[maxn],f[maxn],b[maxn],ans[maxn],vis[maxn];
vector<ll>G[maxn];
vector<ll>W[maxn];

void Read()
{
    ll p,w;
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++) scanf("%lld",a+i);
    for(ll i=2;i<=n;i++)
    {
        scanf("%lld %lld",&p,&w);
        G[p].push_back(i);
        W[p].push_back(w);
        f[i]=p;
    }
}

ll cmp(const ll i,const ll j){return i>j;}

ll sum;
vector<ll>DI,ID;

void ADD(ll x,ll y)
{
    b[f[x]]--;
    b[f[y]]++;
}

void dfs(ll u)
{
    ll k=lower_bound(DI.begin(),DI.end(),a[u]-sum,cmp)-DI.begin();
    vis[u]=1;
    if(k!=(ll)DI.size()) ADD(ID[k],u);
    DI.push_back(-sum);
    ID.push_back(u);
    for(unsigned int i=0;i<G[u].size();i++)
    {
        ll v=G[u][i];
        ll w=W[u][i];
        sum+=w;
        dfs(v);
        sum-=w;
    }
    DI.pop_back();
    ID.pop_back();
    vis[u]=0;
}

void Get_ans(ll u)
{
    for(unsigned int i=0;i<G[u].size();i++)
    {
        ll v=G[u][i];
        Get_ans(v);
        b[u]+=b[v];
    }
}

int main()
{
    Read();
    dfs(1);
    sum=0;
    Get_ans(1);
    for(ll i=1;i<=n;i++) printf("%lld%c",b[i],i==n?'\n':' ');
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值