• 【bzoj1486】【[HNOI2009]梦幻布丁】启发式链表合并(详解)


    这里写图片描述
    (画师当然是武内崇啦)

    Description
    N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
    Input
    第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2…An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0
    Output
    针对第二类操作即询问,依次输出当前有多少段颜色.
    Sample Input
    4 3
    1 2 2 1
    2
    1 2 1
    2
    Sample Output
    3
    1

    最近在复习数据结构,本来打算找一道平衡树的题来做,在黄学长的博客里看到这道题。结果发现和平衡树其实没有关系。。。

    看到这个题的第一想法是暴力:每次o(n)修改或查询
    然而o(n^2)肯定会爆(虽然题目不给范围神坑)。我们希望能够通过某些手段来降log。首先是想到线段树,因为线段树可以解决区间内连续色段,但是我们发现这道题是针对整个序列而言,且修改无法用线段树优化。

    那怎么办呢?我们发现这道题是将所有颜色为x的改为y,总共有效的修改数量是初始时的颜色种数(最多n)。其实这相当于将颜色x与颜色y合并,且之后不会再拆开。所以说这就是合并的问题啦~(废话了这么久。。)

    但是该如何合并呢?我们将同一种颜色的布丁用链表连起来,合并的时候是o(1)的。但是对于合并时ans的更新是o(n)的(对于每一个都判断修改后是否与左右连接)。总的来说,就是每次合并时的复杂度“被修改的颜色的布丁个数”。

    这个是可以优化的,就是用启发式合并(把小的往大的合并)。这样就是o(nlogn)的了。证明就搬一下黄学长的;

    1:每次O(N)
    2:每次合并后,队列长度一定大于等于原来短的长度的两倍。
    这样相当于每次合并都会让短的长度扩大一倍以上,
    最多扩大logN次,所以总复杂度O(NlogN),每次O(logN)。

    但是由于为了启发式合并,我们改变了合并方向。需要用一个f[i]数组来存 调用i颜色时真正用到的颜色。

    下面谈谈链表:
    我以前一直都不清楚链表到底是个什么货。现在好像是明白了:
    有两种链表:
    1、对于每个点,有一个pre(前继)和nxt(后继)。这相当于双向链表
    2、记录一个链表的开头head,对每个点记录一个nxt(下一个)。这相当于是单向链表

    这道题需要访问链表的全部元素,所以用第二种链表。

    (其实之前接触过这链表很多次,但一直不知道这就是链表)

    放代码啦:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int N=1000000+5;
    
    int n,m,c[N],siz[N],f[N],ans=0;
    int head[N],nxt[N],st[N];
    
    void solve(int a,int b){
        for(int i=head[a];i;i=nxt[i]){
            if(c[i+1]==b) ans--;
            if(c[i-1]==b) ans--;
        }
        for(int i=head[a];i;i=nxt[i]) c[i]=b;
        /*这是两种不同的合并方式*/
    //  nxt[st[a]]=head[b];head[b]=head[a];
        nxt[st[b]]=head[a];
    //  head[a]=0,st[a]=0;
        st[b]=st[a];
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&c[i]);
            siz[c[i]]++,f[c[i]]=c[i];
            if(c[i]!=c[i-1]) ans++;
            if(!head[c[i]]) st[c[i]]=i;
            nxt[i]=head[c[i]],head[c[i]]=i;
        }
        int opt,x,y;
        while(m--){
            scanf("%d",&opt);
            if(opt==2) printf("%d
    ",ans);
            else{
                scanf("%d%d",&x,&y);
                if(x==y) continue;//
                if(siz[f[x]]==0) continue;
                if(siz[f[x]]>siz[f[y]]) swap(f[x],f[y]);
                if(siz[f[x]]==0) continue;
                siz[f[x]]+=siz[f[y]],siz[f[x]]=0;//+=
                solve(f[x],f[y]);
            }
        }
        return 0;
    }
  • 相关阅读:
    js 变量的声明能提升 初始化不会提升
    老公教我写分页
    响应式布局
    闭包优缺点
    正则表达式验证邮箱格式
    DDL表和库管理语言
    DML数据库操作语言
    python实现求第K小
    硬币凑数
    MySQL学习的表单定义
  • 原文地址:https://www.cnblogs.com/LinnBlanc/p/7763119.html
Copyright © 2020-2023  润新知