启发式合并

算法理解

对于:

  1. 一开始有很多集合
  2. 只支持合并操作
  3. 每个集合有属性,合并后重新计算属性
    的问题,我们可以进行启发式合并
启发式合并复杂度证明:

证明启发式合并,即证明每个点被访问次数不超过 \(log\)

T1:

先把总答案算出来

我们对于每一种颜色,维护一条链,合并时,将两条链收尾相接,接的时候,判断短的链的各个点是否与另一条链上的点相邻(即判断短的链上的相邻位置是否有和长链相同颜色的点)然后减去答案即可,

没调过,60分,恭喜我学会了对拍并且也没有调过

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e6+5;
int n,m,ans;
int en[M],len[M],b[M],a[N],p[N];
void merge(int x,int y){
    if(x==y||len[x]==0||b[x]==b[y])  return;
    if(len[b[x]]>len[b[y]]){
        swap(b[x],b[y]);
    }
    int s1=b[x],s2=b[y];
    for(int i=en[s1];i;i=p[i]){
        // printf("%d ",i);
        if(s2==a[i-1])  ans--;
        if(s2==a[i+1])  ans--;
    }
    // printf("\n");
    int pri=0;
    for(int i=en[s1];i;i=p[i]){
        a[i]=s2;
        pri=i;
    }
    if(pri)  p[pri]=en[s2],en[s2]=en[s1];
    // printf("pri=%d ens2=%d ens1=%d s1=%d s2=%d\n",pri,en[s2],en[s1],s1,s2);
    b[s1]=s2;
    len[s2]+=len[s1];
    en[x]=len[x]=0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=N;i++)  b[i]=i;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        if(a[i]!=a[i-1])  ans++;
        p[i]=en[a[i]];en[a[i]]=i;
        len[a[i]]++;
    }
    for(int i=1;i<=m;i++){
        int op,x,y;
        scanf("%d",&op);
        if(op==2)  printf("%d\n",ans);
        else{
            scanf("%d%d",&x,&y);
            merge(x,y);
            // printf("%d\n",ans);
            // for(int j=1;j<=9;j++){
            //     printf("%d ",b[j]);
            // }
            // printf("\n");
        }
    }
}

T2:

考虑用 \(cnt[c[i]]\) 来处理每种颜色的数量,显然两个不交的集合合并是没有结合律的(只能暴力),并且也不能记忆化搜索(因为是数组)

所以只能考虑优化枚举顺序(类似于莫队思想)

考虑对于一个节点 \(u\) 它的所有孩子 \(v1,v2,...\)\(cnt\) 可以合并成它的 \(cnt\)(但刚刚说了 \(cnt\) 不能合并),但是我们显然可以保留它的一个孩子的 \(cnt\) 暴力遍历其它孩子的答案,遂选择最大的孩子保留(树剖思想,相当于保留重儿子)

学过树剖的都知道,树剖的复杂度保证即为每一个点到根的轻边数小于 \(log\) 条(证明为每跳一条轻边节点数翻倍)

启发式合并复杂度证明:

假设统计 \(u\) 的答案,即为它的重儿子不需要访问(走过一条重边),轻儿子子树内所有点访问次数+1(走过一条轻边)

刚刚证明每一个点到根的轻边数小于 \(log\) 条,所以每个点被访问次数不超过 \(log\)

trick+1: 对于cnt这种集合运算的删除贡献可以再跑一边原函数,只不过贡献为-1

点击查看代码
#include<bits/stdc++.h>
#define fa(x) dot[x].fa
#define son(x) dot[x].son
#define siz(x) dot[x].siz
#define c(x) dot[x].c
using namespace std;
const int N=1e5+5;
struct node{
    int son,c,fa,siz;
}dot[N];
int n,sum,mxnum,Son;
int cnt[N],ans[N];
vector<int>b[N];
void dfs1(int x,int f){
    fa(x)=f;
    int mson=0,nson=0;
    for(int v:b[x]){
        if(v==f)  continue;
        dfs1(v,x);
        if(siz(v)>nson){
            nson=siz(v);
            mson=v;
        }
        siz(x)+=siz(v);
    }
    siz(x)++;
    son(x)=mson;
}
void add(int x,int val){
    cnt[c(x)]+=val;
    if(cnt[c(x)]>mxnum){
        mxnum=cnt[c(x)];
        sum=c(x);
    }
    else if(cnt[c(x)]==mxnum){
        sum+=c(x);
    }
    for(int v:b[x]){
        if(v==fa(x)||v==Son)  continue;
        add(v,val);
    }
}
void dfs2(int x,int op){
    for(int v:b[x]){
        if(v==son(x)||v==fa(x))  continue;
        dfs2(v,0);
    }
    if(son(x))  dfs2(son(x),1);
    Son=son(x);
    add(x,1);
    Son=0;
    ans[x]=sum;
    if(!op)  add(x,-1),sum=0,mxnum=0;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&c(i));
    }
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        b[u].push_back(v);
        b[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<=n;i++){
        printf("%d ",ans[i]);
    }
}

posted @ 2025-07-23 16:20  daydreamer_zcxnb  阅读(29)  评论(0)    收藏  举报