BZOJ 1483 [HNOI2009]梦幻布丁 链式前向星+启发式合并

题意:
题意很清晰就是没有数据范围- -!
直到现在我都不知道数据范围有多大!
刚开始给定一个序列。
多次操作。
第一种是把值为x的点的值改为y
第二种是询问当前有多少段权值全部相同。
解析:
显然暴力合并的复杂度是O(nm)的,不可取。
所以来考虑黑科技。
如果我们改一改合并的方式。
每一次把短的接在长的上面,那么最少也是使短的长度变为原来的二倍。
所以这种操作如果不卡的话大概是log次?
但是要是卡的话好像挺难?
如果使合并次数增加那么每一次合并的元素就越来越少,所以应该是卡不住的。
所以我们暂且看作是扩大logn次。
所以粗略计算复杂度是O(nlogn).
既然这样我们当然就可以在合并的时候采取启发式合并的方案。
但是对于本题来说。
我们可以把一个颜色对应的所有位置挂成链。
每一次合并暴力添加链。
就很方便找位置了。
另外,为了实现启发式合并,我们需要记录每一种颜色当前代表哪一种颜色,这个很好记录辣。
然后只要不犯二就好啦
代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 1000100
using namespace std;
int n,m,cnt;
int color[N];
int present[N];
int head[N];
int start[N];
int last[N];
int sum[N];
int ans;
struct node
{
    int from,to,next;
}edge[N<<1];
void init()
{
    memset(head,-1,sizeof(head));
    cnt=1;
}
void edgeadd(int from,int to)
{
    edge[cnt].from=from,edge[cnt].to=to,edge[cnt].next=head[from];
    head[from]=cnt++;
}
int main()
{
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&color[i]);
        if(color[i]!=color[i-1])ans++;
        sum[color[i]]++;
        present[color[i]]=color[i];
        edgeadd(color[i],i);
        last[color[i]]=i;
    }
    for(int i=1;i<=m;i++)
    {
        int opt;
        scanf("%d",&opt);
        if(opt==1)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(x==y)continue;
            if(sum[present[x]]>sum[present[y]])
                swap(present[x],present[y]);
            int fx=present[x],fy=present[y];
            if(!sum[fx])continue;
            for(int i=head[fx];i!=-1;i=edge[i].next)
            {
                int to=edge[i].to;
                if(color[to+1]==fy)ans--;
                if(color[to-1]==fy)ans--;
            }
            for(int i=head[fx];i!=-1;i=edge[i].next)
                color[edge[i].to]=fy,edgeadd(fy,edge[i].to);
            head[fx]=-1,sum[fy]+=sum[fx],sum[fx]=0;
        }else printf("%d\n",ans);
    }
} 

你可能感兴趣的:(数据,操作,合并,科技,2009)