Zamjene

link

主要难点在读题。首先理清所谓“可交换集”的意义。可交换集是对于下标而言的,,每个可交换集是一个数对,相当于是把数列中的两个位置连接了起来,而所有可以通过这些边到达的下标集合被称为一片云。一片云是好的当且仅当云中对应下标在原数列中组成的集合和排序后对应下标组成的集合完全相同。很显然假如一片云是好的,那么这片云中的任意元素都可以通过一些方式到达排序后的对应位置,所以一个序列可以通过“可交换集”变成有序序列当且仅当所有云都是好的。既然如此,可以把每一片云当成一个集合,每个操作 \(1\) 就是交换两个集合中某个元素(如果两个元素在同一集合忽略);每个操作 \(2\) 就是合并两个集合,可以直接用并查集和 set 来做;每个操作 \(3\) 就是考虑查找所有的云是否都是好的,直接枚举即可;每个操作 \(4\) 则可以枚举元素验证累加答案即可。于是可以写出暴力的 \(O(N^3)\) 的算法,考场上实测有 \(30\) 分。

考虑如何优化。发现上面那个算法中操作 \(3\) 可以实时更新,也就是在每次操作 \(1,2\) 进行时考虑更新不好云的个数。于是瓶颈变成了如何快速求得操作 \(4\) 的答案。假如用 \(A,B\) 来代表选定的两个位置集合(也就是两朵云),这两个位置集合在原数列中和排序后的数列中对应的元素集合分别为 \(A',B'\)\(A'',B''\),根据题意,需要满足一些条件。首先两朵云都是不好的,也就是 \(A'\ne A'',B'\ne B''\)。然后还需要满足两个集合合并之后形成的云是好的,也就是 \(A'+B'=A''+B''\)。于是发现算法的瓶颈在判断两个集合是否相同上,根据数据范围只能选用极快的哈希。这道题的哈希函数比较特殊,因为我们希望判断的是两个元素是否相同,所以一个元素的哈希值不应该和位置有关系,于是字符串那样的哈希是行不通的。同时这个东西还应该具有可加性,所以我这里用的哈希函数是 \(f(x)=x^K\) ,感性理解这样似乎可以减少哈希冲突(费马大定理在那里摆着呢),更新答案的时候用老朋友 map 维护即可。

代码不够精简,见谅。

#include<bits/stdc++.h>
//#define feyn
#define int long long
using namespace std;
const int N=1000010;
const int S=1e9;
const int P[3]={S+7,S+9,S+11};
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}

struct node{int a[3];};
inline void swap(node &s1,node &s2){node s3=s1;s1=s2;s2=s3;return;}
inline bool operator <(node s1,node s2){
	for(register int i=0;i<3;i++){
		if(s1.a[i]^s2.a[i])return s1.a[i]<s2.a[i];
	}
	return false;
}
inline bool operator !=(node s1,node s2){
	for(register int i=0;i<3;i++){
		if(s1.a[i]^s2.a[i])return true;
	}
	return false;
}
inline bool operator ==(node s1,node s2){
	for(register int i=0;i<3;i++){
		if(s1.a[i]^s2.a[i])return false;
	}
	return true;
}
inline node operator +(node s1,node s2){
	for(register int i=0;i<3;i++){
		s1.a[i]=(s1.a[i]+s2.a[i])%P[i];
	}
	return s1;
}
inline node operator -(node s1,node s2){
	for(register int i=0;i<3;i++){
		s1.a[i]=(s1.a[i]-s2.a[i]+P[i])%P[i];
	}
	return s1;
}
inline node _(node s1){
	for(register int i=0;i<3;i++){
		s1.a[i]=P[i]-s1.a[i];
	}
	return s1;
}
inline node make(int wh){
	node an=(node){1,1,1};
	for(int i=0;i<3;i++){
		for(int j=1;j<=12;j++)an.a[i]=an.a[i]*wh%P[i];
	}
	return an;
}
node a[N],b[N],fir_a[N],fir_b[N];
//b[i]:after sorted seq
int m,n,vv[N];

int f[N],size[N];

int not_same;//不好的云的个数
inline void ss_insert(int wh){if(a[wh]!=b[wh])not_same++;}
inline void ss_del(int wh){if(a[wh]!=b[wh])not_same--;}
int ans_sec;//第二种询问的方案数
map<node,int>mp;
inline void mp_insert(int wh){
	if(a[wh]==b[wh])return;
	node data=a[wh]-b[wh];
	ans_sec+=mp[_(data)]*size[wh];
	mp[data]+=size[wh];
}
inline void mp_del(int wh){
	if(a[wh]==b[wh])return;
	node data=a[wh]-b[wh];
	mp[data]-=size[wh];
	ans_sec-=mp[_(data)]*size[wh];
}

inline int find(int wh){
	return f[wh]==wh?wh:f[wh]=find(f[wh]);
}
inline void merge(int x,int y){
	x=find(x),y=find(y);
	if(x==y)return;
	f[y]=x;
	mp_del(x);mp_del(y);
	ss_del(x);ss_del(y);
	size[x]+=size[y];
	a[x]=a[x]+a[y],b[x]=b[x]+b[y];
	mp_insert(x);
	ss_insert(x);
}
inline void swap_pl(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy){
		swap(fir_a[x],fir_a[y]);
		return;
	}
	ss_del(fx);ss_del(fy);
	mp_del(fx);mp_del(fy);
	a[fx]=a[fx]-fir_a[x]+fir_a[y];
	a[fy]=a[fy]-fir_a[y]+fir_a[x];
	ss_insert(fx);ss_insert(fy);
	mp_insert(fx);mp_insert(fy);
	swap(fir_a[x],fir_a[y]);
}

signed main(){
	
	#ifdef feyn
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	for(int i=1;i<=m;i++){
		read(vv[i]);a[i]=fir_a[i]=make(vv[i]);
	}
	sort(vv+1,vv+m+1);
	for(int i=1;i<=m;i++){
		b[i]=fir_b[i]=make(vv[i]);f[i]=i;size[i]=1;
		ss_insert(i);mp_insert(i);
	}
	int op,x,y;
	while(n--){
		read(op);
		if(op==1){
			read(x);read(y);
			swap_pl(x,y);
		}
		if(op==2){
			read(x);read(y);
			merge(x,y);
		}
		if(op==3){ 
			if(not_same==0)printf("DA\n");
			else printf("NE\n");
		}
		if(op==4){
			printf("%lld\n",ans_sec);
		}
	}
	
	return 0;
}
posted @ 2022-07-27 22:06  Feyn618  阅读(15)  评论(0编辑  收藏  举报