1 条题解
-
0
自动搬运
来自洛谷,原作者为

Siyuan
Dream OIer.搬运于
2025-08-24 21:46:26,当前版本为作者最后更新于2018-11-25 11:59:04,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目链接:BZOJ 1483
有 个布丁摆成一行,每个布丁都有一个颜色 ,进行 次操作。操作共有 种:
- 将颜色为 的布丁全部变成颜色 的布丁。
- 询问当前一共有多少段颜色(例如颜色分别为 的 个布丁一共有 段颜色)。
数据范围:,
Solution
我们可以把每种颜色的布丁集合想象成是一个队列。那么就有若干个长度总和为 的队列,每次操作需要合并 和 。如果我们暴力合并,那么合并一次复杂度最坏为 。
但是有个叫做启发式合并的东西!它的本质很简单:每次把短的合并到长的上面,那么合并一次的复杂度为 。这样看上去貌似没有什么差别嘛 QAQ,接下来我们分析一下均摊复杂度。
考虑用贡献法来分析。我们令两个集合的分别为 和 ,且 ,那么我们把 暴力加入到 中。那么 中的元素所在的集合大小变成 ,也就是说至少变成了原来的两倍。所以每个元素至多被加入 次,总的复杂度为 。
对于这道题目,我们先求出原序列的答案,对于每一种颜色都用类似链表的数据结构串起来,并记录下尾节点。每次修改,都根据启发式合并的方法来暴力合并,然后处理一下此次合并对答案的影响(显然答案是不增的)。
但是如果我们把 染成 并且 ,那么我们应该把 接到 的后面。这样会有一个问题:本次修改后这个链的颜色是 (颜色为 的链被删除了),如果接下来修改颜色 (显然这是合法的),会使得找不到颜色 而只能找到颜色 了。所以我们需要使用一个 数组,表示当我们要寻找颜色 时,实际上需要寻找颜色为 的链。如果遇到上面这种情况就要交换交换 和 。
时间复杂度:
Code
#include <iostream> #include <cstdio> const int N=1e5+5,M=1e6+5; int n,m,c[N],sz[M],st[M],f[M],hd[M],nxt[N],ans; void merge(int x,int y) { for(int i=hd[x];i;i=nxt[i]) ans-=(c[i-1]==y)+(c[i+1]==y); for(int i=hd[x];i;i=nxt[i]) c[i]=y; nxt[st[x]]=hd[y],hd[y]=hd[x],sz[y]+=sz[x]; hd[x]=st[x]=sz[x]=0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { scanf("%d",&c[i]),f[c[i]]=c[i]; ans+=c[i]!=c[i-1]; if(!hd[c[i]]) st[c[i]]=i; ++sz[c[i]],nxt[i]=hd[c[i]],hd[c[i]]=i; } while(m--) { int opt; scanf("%d",&opt); if(opt==2) printf("%d\n",ans); else { int x,y; scanf("%d%d",&x,&y); if(x==y) continue; if(sz[f[x]]>sz[f[y]]) std::swap(f[x],f[y]); if(!sz[f[x]]) continue; merge(f[x],f[y]); } } return 0; }
- 1
信息
- ID
- 2274
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者