洛谷 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)\)合并……

posted @ 2021-02-22 16:37  Vanilla_chan  阅读(138)  评论(0编辑  收藏  举报