zoukankan      html  css  js  c++  java
  • [HNOI2009]梦幻布丁 算法技巧之邻接链

    题目描述

    N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.

    输入输出格式

    输入格式:

    第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2...An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0

    输出格式:

    针对第二类操作即询问,依次输出当前有多少段颜色.

    输入输出样例

    输入样例#1:
    4 3
    1 2 2 1
    2
    1 2 1
    2
    输出样例#1:
    3
    1

    说明

    1<=n,m<=100,000; 0<Ai,x,y<1,000,000

    首先上网络上的题解:

    1:将两个队列合并,有若干队列,总长度为n,直接合并,最坏O(N),
     
    2:启发式合并呢?
     
    每次我们把短的合并到长的上面去,O(短的长度)
     
    咋看之下没有多大区别,
     
    下面让我们看看均摊的情况:
     
    1:每次O(N)
    2:每次合并后,队列长度一定大于等于原来短的长度的两倍。
     
    这样相当于每次合并都会让短的长度扩大一倍以上,
     
    最多扩大logN次,所以总复杂度O(NlogN),每次O(logN)。
     
    然后对于此题
    我们先求出原序列的答案
    每一种颜色搞一条链把该色结点串起来,记录下链条尾结点
    把一种颜色的染成另一种,很简单把它合并过去,然后处理下对于答案的影响
    但是。。。
    比如把1染成2,但是s[1]>s[2],这时我们应该将2合并到1的链后面,但是会遇到一个麻烦的问题,就是这个链头是接1下的,也就是说以后找颜色2,发现没有颜色2只有颜色1。。。
    于是我们应该开一个数组f,表示我们寻找一种颜色时,实际应该找哪个颜色下的链,遇到上面那种情况要交换f[1]和f[2]
     
    以下是个人见解:
    直接上代码,主要关注代码中的注释。
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    #include<cstdlib>
    #include<queue>
    #include<stack>
    #include<ctime>
    using namespace std;
    const int maxn=1000001;
    int n,m,ans;
    int s[maxn],//每一个颜色的个数 
        Next[maxn],//邻接链 
        head[maxn],//邻接链
        map[maxn],//存图  
        dp[maxn],//当前位置的实际颜色 
        first[maxn];//某颜色第一次出现的位置 
    void solve(int a,int b)
    {
        for(int i=head[a];i!=-1;i=Next[i])
        {
            if(map[i+1]==b)ans--;//更改颜色后与后方颜色相同,ans-- 
            if(map[i-1]==b)ans--;//更改颜色后与前方颜色相同,ans-- 
        }//计算对结果的影响 
        for(int i=head[a];i!=-1;i=Next[i])map[i]=b;//更改颜色 
        Next[first[a]]=head[b];head[b]=head[a];s[b]+=s[a];
        head[a]=first[a]=s[a]=0;//将两个邻接链合并,只需更改后继顺序即可 
    }
    int main()
    {
        int i,j;
        scanf("%d%d",&n,&m);
        memset(head,-1,sizeof(head));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&map[i]);
            dp[map[i]]=map[i];
            if(map[i]!=map[i-1])ans++;
            if(head[map[i]]==-1)first[map[i]]=i;
            s[map[i]]++;
            Next[i]=head[map[i]];
            head[map[i]]=i;
        }//输入,赋初值 ,创建邻接链 
        for(i=1;i<=m;i++)
        {
            int a,b,x;
            scanf("%d",&x);
            if(x==2)printf("%d
    ",ans);
            else
            {
                scanf("%d%d",&a,&b);
                if(a==b)continue;
                if(s[dp[a]]>s[dp[b]])
                    swap(dp[a],dp[b]);
                a=dp[a];b=dp[b];
    //dp数组的意义在于每次都是选颜色相对少的集合进行合并以提高效率,但由于之后可能会有与这个颜色
    //相关的变换所以需要一个数组来维护当前颜色对应的实际颜色 
                if(s[a]==0)continue;
                s[b]+=s[a];s[a]=0;
                solve(a,b);
            }
        }
        return 0;
    }
  • 相关阅读:
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 455 分发饼干
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    Java实现 LeetCode 454 四数相加 II
    FFmpeg解码H264及swscale缩放详解
    linux中cat more less head tail 命令区别
    C语言字符串操作总结大全(超详细)
    如何使用eclipse进行嵌入式Linux的开发
  • 原文地址:https://www.cnblogs.com/huangdalaofighting/p/6789560.html
Copyright © 2011-2022 走看看