洛谷 P3201 [HNOI2009] 梦幻布丁
洛谷 P3201 [HNOI2009] 梦幻布丁
祭我AC的第十道紫题。
听了一下午的
才调出来。
题意分析
给一个序列a,有两种操作——
1 x y
将序列中所有x全部变成y
2
查询当前序列有多少段。
\(1\le n,m\le 10^5,1\le a_i,x,y\le 10^6\)。
Solution
考虑每修改一个位置带来的贡献(亦或者说是影响?)。我们每将一个\(a_i=x\)修改为了\(y\),那么如果\(a_{i-1} =y\)或\(a_{i+1}=y\)都会使段数\(+1\)。
但是这样虽然简化的操作\(2\),但是\(1\)还是需要\(O(n)\)的时间来维护。
但是如果我们修改颜色时,将数量少的那一种颜色修改为数量多的那一种,这样修改次数就大大减少了。这种合并方案属于启发式合并。
至于用什么方式来合并。思来思去,还是用链表解决最优。(当然什么set,vector,线段树合并都行)。
设链表的值为\(f_i\),大小为\(size_i\),起点为\(head_i\),指针为\(next_i\)。
开始时,先将同一种颜色放到一个链表中,并计算原序列有多少段。
读入\(x,y\)并比较链表大小(注意交换的是\(f_x,f_y\),相当于交换了两个链表)。
统计贡献之后再修改小链表的颜色,把小链表塞到打链表的前面,更新大链表的\(size,head\)。
清空小链表的\(head,size\)。
Code
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<vector>
#include<limits.h>
#define IL inline
#define re register
#define LL long long
#define ULL unsigned long long
#ifdef TH
#define debug printf("Now is %d\n",__LINE__);
#else
#define debug
#endif
using namespace std;
template<class T>inline void read(T&x)
{
char ch=getchar();
int fu;
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
x*=fu;
}
inline int read()
{
int x=0,fu=1;
char ch=getchar();
while(!isdigit(ch)&&ch!='-') ch=getchar();
if(ch=='-') fu=-1,ch=getchar();
x=ch-'0';ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*fu;
}
int G[55];
template<class T>inline void write(T x)
{
int g=0;
if(x<0) x=-x,putchar('-');
do{G[++g]=x%10;x/=10;}while(x);
for(int i=g;i>=1;--i)putchar('0'+G[i]);putchar('\n');
}
#define N 100010
int n,m;
int a[N];
int ans;
int f[N*10],sze[N*10];
int head[N*10],nxt[N*10];
int main()
{
// freopen("P3201.in","r",stdin);
// freopen("P3201.out","w",stdout);
n=read();
m=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
f[a[i]]=a[i];
sze[a[i]]++;
nxt[i]=head[a[i]];
head[a[i]]=i;
if(a[i]!=a[i-1]) ans++;
}
int op,x,y;
while(m--)
{
op=read();
if(op==1)
{
x=read();
y=read();
if(x==y) continue;
if(sze[f[x]]>sze[f[y]]) swap(f[x],f[y]);//Æô·¢Ê½ºÏ²¢
x=f[x];
y=f[y];
for(int i=head[x];i;i=nxt[i])
{
if(a[i+1]==y) ans--;
if(a[i-1]==y) ans--;
}
int j=0;
for(int i=head[x];i;i=nxt[i]) a[j=i]=y;
if(head[x]) nxt[j]=head[y],head[y]=head[x];
sze[y]+=sze[x];
sze[x]=0;
head[x]=0;
}
else write(ans);
// for(int i=1;i<=n;i++) cout<<a[i]<<" ";
// cout<<endl;
}
return 0;
}
Attention
注意特判\(x=y\)。
这个合并我实在是不会用vector,vector貌似不能\(O(1)\)合并……