7.17 学习笔记

1 RMQ Range Mximum/Minimun Query

区间最值问题。

1.1 st 表

\(f_{i,j}\) 表示从 \(i\) 开始,取 \(2^j\) 个数的最小值。形式化来说,取得是 \([i,i+2^j-1]\) 的最小值。

其实是一个倍增的过程。可以做到 \(O(n\log n)\) 预处理,\(O(1)\) 查询最值。

同样运用倍增思想的还有快速幂。

过于简单,不放代码。

2 LCA

2.1 倍增求 LCA

我们只需要利用倍增的思想预处理出所有节点 \(2\) 的幂网上的父亲是谁,以及每个点的深度,然后往上跳就可以。

树上倍增法同时也是我们在树上收集信息的常用方法。

预处理复杂度 \(O(n\log n)\) 。询问复杂度 \(O(\log n)\)

过于简单,不过代码。

2.2 \(O(1)\) 求 LCA

这里给出欧拉序的严格定义。如图:

也就是说,我们 dfs 每次经过都要把该点加入欧拉序。我们发现每一个点加入的次数是所有儿子的个数加 \(1\)

那么除了根节点不给其它节点做儿子外,其它节点都给别人做儿子,所以除了根节点,每一个点的贡献都是 \(2\)

这个 \(2\) 的贡献,一个是给别人当儿子,另一个是其本身。

容易证明,在欧拉序上,设 \(x\) 第一次出现的编号为 \(x_0\)\(y\) 第一次出现的编号为 \(y_1\) ,那么 \(x,y\) 的 lca 在欧拉序上应该是欧拉序上\(x_0,y_0\) 之间深度最小的点。证明这个东西只需要证明 lca 一定在这个区间并且在这个区间内不会出现比 lca 深度小或相等的点。这两者都容易证明。

以前 \(O(1)\) lca 并没有打过,所以我们这里把代码写一下。

不知道是不是我打的常数太大了,跑的没有树链剖分快,也许是洛谷模板题询问有点少。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1000010
#define M 3000010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Min(T a,T b){
    return a<b?a:b;
}

struct edge{
    int to,next;
    inline void intt(int to_,int ne_){
        to=to_;next=ne_;
    }
};
edge li[M];
int head[N],tail;

inline void add(int from,int to){
    li[++tail].intt(to,head[from]);
    head[from]=tail;
}

struct point_deep{
    int id,deep;
    inline point_deep() {}
    inline point_deep(int id,int deep) : id(id),deep(deep) {}
    inline bool operator < (const point_deep &b) const{
        return deep<b.deep;
    }
};
point_deep xulie[N];

int deep[N],tailx,FirApp[N];

inline void dfs(int k,int fa){
    deep[k]=deep[fa]+1;
    xulie[++tailx]=point_deep(k,deep[k]);
    if(!FirApp[k]) FirApp[k]=tailx;
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to;
        if(to==fa) continue;
        dfs(to,k);
        xulie[++tailx]=point_deep(k,deep[k]);
    }
}

point_deep st[N][21];

inline void build_st(){
    for(int i=1;i<=tailx;i++) st[i][0]=xulie[i];
    for(int i=1;i<=20;i++){
        for(int j=1;j+(1<<i)-1<=tailx;j++){
            st[j][i]=Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
        }
    }
}

inline point_deep query(int l,int r){
    int len=log2(r-l+1);
    return Min(st[l][len],st[r-(1<<len)+1][len]);
}

int n,m,s;

int main(){
    read(n);read(m);read(s);
    for(int i=1;i<=n-1;i++){
        int from,to;read(from);read(to);
        add(from,to);add(to,from);
    }
    dfs(s,0);
    build_st();
    for(int i=1;i<=m;i++){
        int l,r;
        read(l);read(r);
        int fl=FirApp[l],fr=FirApp[r];
        if(fl>fr) swap(fl,fr);
        point_deep nowans=query(fl,fr);
        printf("%d\n",nowans.id);
    }
    return 0;
}

2.3 LCA 的应用

LCA 有很多应用,包括但不限于树上差分,求树上两点距离,轻重链剖分也需要用 lca。

这些东西我好像都写过博客,所以这里不再赘述。

3 二维 st 表

对于一个二维数组有对应的二维 st 表。设 \(f_{i,j,a,b}\) 表示横坐标在 \([i,i+2^a-1]\) 纵坐标在 \([j,j+2^b+1]\) 之间的最值。

转移也是类似的。我们直接按照二进制分割就可以。

代码:

咕咕咕

4 例题

过水的题不整理。

4.1 删数问题加强版

链接

我们考虑选首位,首先一定要让首位选最好的,在给定 \(m\) 的情况下,首位能选的位置是有限的,选最小的数,这是一个经典的 RMQ 问题,所以我们可以用 st 表做。接下来更新下一个的备选区间就可以。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> T Min(T a,T b){
    return a>b?b:a;
}

typedef pair<int,int> P;
P st[N][10];

int ans[N],tail,a[N],len,m,lg2[N];
char s[N];

inline void build_st(){
    memset(st,0,sizeof(st));
    lg2[0]=-1;for(int i=1;i<=len;i++) lg2[i]=lg2[i/2]+1;
    for(int i=1;i<=len;i++){
        st[i][0].first=a[i];st[i][0].second=i;
        // printf("i:%d j:0 st:%d %d\n",i,st[i][0].first,st[i][0].second);
    }
    for(int i=1;i<=10;i++){
        for(int j=1;j+(1<<i)-1<=len;j++){
            st[j][i]=Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
            // printf("i:%d j:%d st:%d %d\n",i,j,st[j][i].first,st[j][i].second);
        }
    }
}

inline P query(int l,int r){
    int lglen=lg2[r-l+1];
    return Min(st[l][lglen],st[r-(1<<lglen)+1][lglen]);
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    while(cin>>s>>m){
        tail=0;
        len=strlen(s);
        for(int i=1;i<=len;i++) a[i]=s[i-1]-'0';
        int l=1;m=len-m;build_st();
        for(int i=m;i>=1;i--){
            P now=query(l,len-m+1);
            ans[++tail]=now.first;
            l=now.second+1;m--;
        }
        int head=0;
        while(head+1<tail&&ans[head+1]==0) head++;
        for(int i=head+1;i<=tail;i++) printf("%d",ans[i]);
        putchar('\n');
    }
}

4.2 Difference Is Beautiful

链接

首先发现答案有二分性,然后考虑以固定点为端点向右能够延伸的最大长度是多少,这个东西可以用双指针 \(O(n)\) 统计,我们考虑二分答案。设当前二分为 \(mid\) ,设当前询问区间为 \(l,r\) ,所以我们可以用 \(st\) 表维护长度最值,对于一个区间,我们只需要求 \([l,r-mid+1]\) 的最值就可以了,看这个最值是否大于等于 \(mid\) ,为什么是 \(r-mid+1\) 是因为要保证区间长度至少要保证有 \(mid\)

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 2000010
using namespace std;

const int INF=0x3f3f3f3f;
const int mod=1e6;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline void write(T a){
    if(a<0){putchar('-');a=-a;}
    if(!a) return;
    write(a/10);putchar(a%10+'0');
}

template<typename T> inline T Max(T a,T b){
    return a<b?b:a;
}

int n,m,a[N],cnt[M],b[N],log_2[N];
int st[N][21];
// build
inline void build_st(){
    log_2[0]=-1;for(int i=1;i<=n;i++) log_2[i]=log_2[i/2]+1;
    for(int i=1;i<=n;i++) st[i][0]=b[i];
    for(int i=1;i<=log_2[n];i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            st[j][i]=Max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
        }
    }
}

inline int query(int l,int r){
    int len=log_2[r-l+1];
    return Max(st[l][len],st[r-(1<<len)+1][len]);
}

inline bool check(int z,int y,int mid){
    if(mid>(y-z+1)) return 0;
    int res=query(z,y-mid+1);
    if(res>=mid) return 1;
    else return 0;
}

inline int erfen(int z,int y){
    int l=1,r=n,ans;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(z,y,mid)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}

int main(){
    read(n);read(m);
    for(int i=1;i<=n;i++){read(a[i]);a[i]+=mod;}
    for(int l=1,r=0;l<=n;l++){
        while(r+1<=n&&cnt[a[r+1]]<=0){r++;cnt[a[r]]++;}
        b[l]=r-l+1;cnt[a[l]]--;
    }
    build_st();
    for(int i=1;i<=m;i++){
        int z,y;read(z);read(y);z++;y++;
        int ans=erfen(z,y);
        printf("%d\n",ans);
    }
    return 0;
}

4.3 CF1301E Nanosoft

链接

咕咕咕

4.4 UVA1707 Surveillance

链接

咕咕咕

4.5 CF609E Minimum spanning tree for each edge

链接

我们考虑先求出最小生成树,设边权和为 \(sum\)

对于一条边,如果他在最小生成树上,那么就可以不做任何操作。

否则,我们在树上找到这两个点路径上的最大值,建取最大值,然后把我们当前的这条边加进去。

上面这个贪心的想法正确性显然,因为我们要保证权值尽量小。

至于两个点路径上的最大值,我们可以用树上倍增法来预处理。

在与处理时注意特判:

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){
    return a<b?b:a;
}

struct UnionFindSet{
    int fa[N];
    inline void init(int n){
        for(int i=1;i<=n;i++) fa[i]=i;
    }
    inline int find(int x){
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    inline bool Merge_IsTheSame(int a,int b){
        int faa=find(a),fab=find(b);
        if(faa==fab) return 1;
        fa[faa]=fab;return 0;
    }
};
UnionFindSet ufs;

struct Bian{
    int from,to,w,id;
    inline bool operator < (const Bian &b)const{
        return w<b.w;
    }
};
Bian bian[M];

bool IsChoose[M];

struct edge{
    int to,next,w;
    inline void intt(int to_,int ne_,int w_){
        to=to_;next=ne_;w=w_;
    }
};
edge li[M<<1];
int head[N],tail;

inline void add(int from,int to,int w){
    li[++tail].intt(to,head[from],w);
    head[from]=tail;
}

int n,m;

inline int Kruscal(){
    ufs.init(n);
    sort(bian+1,bian+m+1);
    int cnt=0,nowans=0;
    for(int i=1;i<=m;i++){
        if(!ufs.Merge_IsTheSame(bian[i].from,bian[i].to)){
            IsChoose[i]=1;nowans+=bian[i].w;
            add(bian[i].from,bian[i].to,bian[i].w);add(bian[i].to,bian[i].from,bian[i].w);
        }
        if(tail==2*n-2) break;
    }
    return nowans;
}

int ans[M];

int fa[N][21],MaxEdge[N][21],deep[N];

inline void dfs(int k,int fat){
    fa[k][0]=fat;for(int i=1;i<=20;i++) fa[k][i]=fa[fa[k][i-1]][i-1];
    deep[k]=deep[fat]+1;
    for(int i=1;i<=20;i++) MaxEdge[k][i]=Max(MaxEdge[k][i-1],MaxEdge[fa[k][i-1]][i-1]);
    for(int x=head[k];x;x=li[x].next){
        int to=li[x].to,w=li[x].w;
        if(to==fat) continue;
        MaxEdge[to][0]=w;dfs(to,k);
    }
}

inline int GetMax(int a,int b){
    int maxx=-INF;
    if(deep[a]<deep[b]) swap(a,b);
    for(int i=20;i>=0;i--)
        if(deep[fa[a][i]]>=deep[b]){maxx=Max(maxx,MaxEdge[a][i]);a=fa[a][i];}
    for(int i=20;i>=0;i--)
        if(fa[a][i]!=fa[b][i]){
            maxx=Max(maxx,Max(MaxEdge[a][i],MaxEdge[b][i]));a=fa[a][i];b=fa[b][i];
        }
    if(a!=b) maxx=Max(MaxEdge[a][0],Max(maxx,MaxEdge[b][0]));
    return maxx;
}

signed main(){
    read(n);read(m);
    for(int i=1;i<=m;i++){
        read(bian[i].from);read(bian[i].to);read(bian[i].w);bian[i].id=i;
    }
    int nowans=Kruscal();dfs(1,0);
    for(int i=1;i<=m;i++){
        if(IsChoose[i]){ans[bian[i].id]=nowans;continue;}
        int now=GetMax(bian[i].from,bian[i].to);
        ans[bian[i].id]=nowans-now+bian[i].w;
    }
    for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
    return 0;
}