Loading

7.17 学习笔记

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]\) 之间的最值。

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

需要注意是否从 \(0\) 开始。

过程具体来说,我们先预处理点的情况,再预处理一个长条的情况,最后处理变宽的情况。

至于查询,思路大致和一维 st 表一样。

代码:

inline void build_st(){
    int maxx=Max(n,m);
    lg2[0]=-1;for(int i=1;i<=maxx;i++) lg2[i]=lg2[i/2]+1;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) st[i][j][0][0]=val[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=lg2[m];j++)
            for(int k=1;k+(1<<j)-1<=m;k++){
                st[i][k][0][j]=Max(st[i][k][0][j-1],st[i][k+(1<<(j-1))][0][j-1]);
            }
                
    for(int i=1;i<=lg2[n];i++)
        for(int j=0;j<=lg2[m];j++)
            for(int k=1;k+(1<<i)-1<=n;k++)
                for(int q=1;q+(1<<j)-1<=m;q++){
                    st[k][q][i][j]=Max(st[k][q][i-1][j],st[k+(1<<(i-1))][q][i-1][j]);
                }
                    
}

inline int query(int sx,int sy,int xx,int xy){
    int len1=lg2[xx-sx+1],len2=lg2[xy-sy+1];
    int maxx=-INF;
    maxx=Max(maxx,Max(st[sx][sy][len1][len2],st[xx-(1<<len1)+1][xy-(1<<len2)+1][len1][len2]));
    maxx=Max(maxx,Max(st[xx-(1<<len1)+1][sy][len1][len2],st[sx][xy-(1<<len2)+1][len1][len2]));
    return maxx;
}

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

链接

这个题竟然没有人用二维 st 表过,这里写个题解。

以下,我们称一个 logo 最中间的那个点(不是方块)为关键点。大致思路如下:

  1. 我们预处理出每一个点作为关键点时,最大方块边长除以 \(2\) 为多少。这个可以二分。
  2. 对于每一个询问,答案显然具有二分性,所以我们可以二分来做。

因为我们在第二次二分中需要 \(O(1)\) 判断,所以我们需要提前预处理出二维 st 表。

第一次二分中的判断我们可以用二维前缀和优化到 \(O(1)\)

代码:

#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 501
#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?a:b;
}

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

char s[N][N];
int sum[4][N][N],st[N][N][10][10],val[N][N],lg2[N],n,m,q;
//sum: 0 is red, 1 is green, 2 is blue, 3 is yellow
//st is the maxest

inline void build_st(){
    int maxx=Max(n,m);
    lg2[0]=-1;for(int i=1;i<=maxx;i++) lg2[i]=lg2[i/2]+1;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) st[i][j][0][0]=val[i][j];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=lg2[m];j++)
            for(int k=1;k+(1<<j)-1<=m;k++){
                st[i][k][0][j]=Max(st[i][k][0][j-1],st[i][k+(1<<(j-1))][0][j-1]);
            }
                
    for(int i=1;i<=lg2[n];i++)
        for(int j=0;j<=lg2[m];j++)
            for(int k=1;k+(1<<i)-1<=n;k++)
                for(int q=1;q+(1<<j)-1<=m;q++){
                    st[k][q][i][j]=Max(st[k][q][i-1][j],st[k+(1<<(i-1))][q][i-1][j]);
                }
                    
}

inline int query(int sx,int sy,int xx,int xy){
    int len1=lg2[xx-sx+1],len2=lg2[xy-sy+1];
    int maxx=-INF;
    maxx=Max(maxx,Max(st[sx][sy][len1][len2],st[xx-(1<<len1)+1][xy-(1<<len2)+1][len1][len2]));
    maxx=Max(maxx,Max(st[xx-(1<<len1)+1][sy][len1][len2],st[sx][xy-(1<<len2)+1][len1][len2]));
    return maxx;
}

inline bool check(int x,int y,int mid){
    int dx,dy,lx=x,ly=y;
    x=lx;y=ly;dx=x-mid,dy=y-mid;
    if(sum[0][x][y]+sum[0][dx][dy]-sum[0][x][dy]-sum[0][dx][y]!=mid*mid) return 0;
    x=lx+mid;y=ly;dx=x-mid;dy=y-mid;
    if(sum[3][x][y]+sum[3][dx][dy]-sum[3][x][dy]-sum[3][dx][y]!=mid*mid) return 0;
    x=lx;y=ly+mid;dx=x-mid;dy=y-mid;
    if(sum[1][x][y]+sum[1][dx][dy]-sum[1][x][dy]-sum[1][dx][y]!=mid*mid) return 0;
    x=lx+mid;y=ly+mid;dx=x-mid;dy=y-mid;
    if(sum[2][x][y]+sum[2][dx][dy]-sum[2][x][dy]-sum[2][dx][y]!=mid*mid) return 0;
    return 1;
}

inline int erfen(int x,int y){
    int l=0,r,res;
    r=Min(Min(x,y),Min(n-x,m-y));
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(x,y,mid)){res=mid;l=mid+1;}
        else r=mid-1;
    }
    return res;
}

inline bool AnsCheck(int sx,int sy,int xx,int xy,int mid){
    sx+=mid;sy+=mid;xx-=mid;xy-=mid;
    if(xx<sx||xy<sy) return 0;
    int maxx=query(sx,sy,xx,xy);
    if(maxx>=mid) return 1;
    return 0;
}

inline int BinaryAns(int sx,int sy,int xx,int xy){
    int l=0,r,res;
    r=Min(xx-sx,xy-sy);if(r&1) r--;
    while(l<=r){
        int mid=(l+r)>>1;
        if(AnsCheck(sx,sy,xx,xy,mid)){res=mid;l=mid+1;}
        else r=mid-1;
    }
    return res;
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);

    read(n);read(m);read(q);
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(s[i][j]=='R') sum[0][i][j]=1;
            else if(s[i][j]=='G') sum[1][i][j]=1;
            else if(s[i][j]=='B') sum[2][i][j]=1;
            else if(s[i][j]=='Y') sum[3][i][j]=1;
        }
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            for(int k=0;k<=3;k++)
                sum[k][i][j]+=sum[k][i][j-1]+sum[k][i-1][j]-sum[k][i-1][j-1];
    
    for(int i=1;i<=n+1;i++)
        for(int j=1;j<=m+1;j++){
            val[i][j]=erfen(i-1,j-1);
        }
    n++;m++;

    // for(int i=1;i<=n;i++)
    //     for(int j=1;j<=m;j++)
    //         printf("i:%d j:%d val:%d\n",i,j,val[i][j]);

    build_st();
    while(q--){
        int sx,sy,xx,xy;read(sx);read(sy);read(xx);read(xy);xx++;xy++;
        int ans=BinaryAns(sx,sy,xx,xy);
        printf("%d\n",ans*ans*4);
    }
    return 0;
}

4.4 UVA1707 Surveillance

链接

遇到环上问题,我们先不考虑环,先考虑链。如果是链的话,我们直接预处理出把每个位置覆盖住的线段最右端点是哪个位置,然后贪心就可以了。这个做法 \(O(n)\)

我们考虑断环为链,复制一段接在尾端,如果我们直接枚举左端点暴力做的话是 \(O(n^2)\) ,考虑用倍增优化这个过程,预处理出走 \(2^i\) 步能走到的位置,就可以做了。时间复杂度 \(O(n\log n)\)

代码:

#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 2000010
#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 Max(T a,T b){
    return a<b?b:a;
}

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

int n,k,a[N],nxt[N],nx[N][21],lg2[N],ans=INF;

inline void clear(){
    for(int i=1;i<=2*n;i++) nxt[i]=a[i]=0;ans=INF;
    memset(nx,0,sizeof(nx));
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    lg2[0]=-1;for(int i=1;i<=2*1e6;i++) lg2[i]=lg2[i/2]+1;
    while(cin>>n>>k){
        for(int i=1;i<=k;i++){
            int l,r;read(l);read(r);
            if(r<l) r+=n;
            a[l]=Max(a[l],r);
        }
        int maxr=0;
        for(int i=1;i<=2*n;i++){
            if(a[i]){
                if(nxt[i-1]!=INF) nxt[i]=Max(nxt[i-1],a[i]);
                else nxt[i]=a[i];
                maxr=Max(maxr,a[i]);
            }
            else if(maxr>=i) nxt[i]=nxt[i-1];
            else nxt[i]=INF;
            nx[i][0]=nxt[i]+1;
        }
        for(int i=1;i<=lg2[2*n];i++){
            for(int j=1;j<=2*n;j++){
                if(nx[j][i-1]>=INF){
                    nx[j][i]=INF;
                }
                else nx[j][i]=nx[nx[j][i-1]][i-1];
            }
        }
        for(int i=1;i<=n;i++){
            int now=i,cnt=0;
            for(int j=lg2[2*n];j>=0;j--){
                if(nx[now][j]<i+n){
                    now=nx[now][j];
                    cnt+=(1<<j);
                }
            }
            if(nx[now][0]<=INF){ans=Min(ans,cnt+1);}
        }
        if(ans!=INF) printf("%d\n",ans);
        else printf("impossible\n");
        clear();
    }
    return 0;
}

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

4.6 MEX Tree

链接

我们考虑钦定这棵树以 \(0\) 为根,然后手算出 MEX 为 \(0,1\) 时的答案。其余答案一定经过根节点,我们维护这条路径就可以。

分类讨论细节比较多,是一道好题。

#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 400010
#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?a:b;
}

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

typedef pair<int,int> P;

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

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

int t,n,siz[N],ans[N],deep[N],tailx,lg2[N],FirApp[N],x=1,y=1,spe;
P xulie[N<<1],st[N][21];

inline void dfs(int k,int fa){
    siz[k]=1;deep[k]=deep[fa]+1;xulie[++tailx]=make_pair(deep[k],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);
        siz[k]+=siz[to];xulie[++tailx]=make_pair(deep[k],k);
    }
}

inline void build_st(){
    lg2[0]=-1;for(int i=1;i<=tailx;i++) lg2[i]=lg2[i/2]+1;
    for(int i=1;i<=tailx;i++) st[i][0]=xulie[i];
    for(int i=1;i<=lg2[tailx];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 P query(int l,int r){
    int len=lg2[r-l+1];
    return Min(st[l][len],st[r-(1<<len)+1][len]);
}

inline int GetLca(int a,int b){
    P res=query(Min(FirApp[a],FirApp[b]),Max(FirApp[a],FirApp[b]));
    return res.second;
}

inline void clear(){
    for(int i=1;i<=n+1;i++) ans[i]=head[i]=0;
    tail=0;x=y=1;tailx=0;
}

inline void prework(){
    ans[2]+=(1ll*(siz[1]-siz[2])*(siz[1]-siz[2]-1)/2);
    for(int x=head[1];x;x=li[x].next){
        int to=li[x].to;
        ans[1]+=(1ll*siz[to]*(siz[to]-1)/2);
        int lca=GetLca(2,to);
        if(lca==to) ans[2]-=(1ll*(siz[to]-siz[2])*(siz[to]-siz[2]-1)/2);
        else ans[2]-=1ll*siz[to]*(siz[to]-1)/2;
    }
}

inline bool insert(int k){
    int lca1=GetLca(k,x),lca2=GetLca(k,y);
    if(y!=1){
        if(lca1==k||lca2==k) return 1;
        else if(lca1==x) x=k;
        else if(lca2==y) y=k;
        else return 0;
    }
    else{
        if(lca1==k) return 1;
        else if(lca1==x) x=k;
        else if(lca1==1) y=k;
        else return 0;
    }
    if(deep[x]<deep[y]) swap(x,y);
    return 1;
}
// mex is k
inline int GetAns(int k){
    int lca1=GetLca(k,x),lca2=GetLca(k,y);
    int res1,res2;
    if(y!=1){
        res1=siz[x];res2=siz[y];
        if(lca1==k||lca2==k) return 0;
        else if(lca1==x) res1-=siz[k];
        else if(lca2==y) res2-=siz[k];
    }
    else{
        res1=siz[x];res2=siz[y]-spe;
        if(lca1==k||lca2==k) return 0;
        else if(lca1==x) res1-=siz[k];
        else if(lca1==1) res2-=siz[k];
    }
    return res1*res2;
}

signed main(){
    read(t);
    while(t--){
        read(n);
        for(int i=1;i<=n-1;i++){
            int from,to;read(from);read(to);from++;to++;
            add(from,to);add(to,from);
        }
        dfs(1,0);build_st();
        prework();// already solve ans[1] ans[2]
        for(int i=3;i<=n+1;i++){
            if(!insert(i-1)) break;
            if(i==n+1){ans[i]=1;break;}
            if(i==3){
                for(int x=head[1];x;x=li[x].next){
                    int to=li[x].to;
                    int lca=GetLca(to,2);
                    if(lca==to){spe=siz[to];break;}
                }
            }
            ans[i]=GetAns(i);
        }
        for(int i=1;i<=n+1;i++) printf("%lld ",ans[i]);
        if(t) putchar('\n');
        clear();
    }
    return 0;
}//
posted @ 2021-07-18 07:35  hyl天梦  阅读(66)  评论(0编辑  收藏  举报