[JZOJ 5910] [NOIP2018模拟10.18] DuLiu 解题报告 (并查集+思维)
题目链接:
https://jzoj.net/senior/#contest/show/2530/0
题目:
LF是毒瘤出题人中AK IOI2019,不屑于参加NOI的唯一的人。他对人说话,总是满口垃圾题目者也,教人半懂不懂的。因为他姓李,别人便从QQ群上的“毒瘤李Fee”这半懂不懂的话里,替他取下一个绰号,叫做李Fee。
李Fee一到机房,所有做题的人便都看着他笑,有的叫道,“李Fee,你又来出毒瘤题了!”他不回答,对验题人说,“我又出了两道题,给我验验。”便排出一排毒瘤题。大家又故意的高声嚷道,“你又暴露奸商本性拿毒瘤题骗钱剥削验题人了!”李Fee睁大眼睛说,“你怎么这样凭空污人清白……”“什么清白?我前天亲眼见你出了道毒瘤骗钱题,被PTY把std吊着打。” 李Fee便涨红了脸,额上的青筋条条绽出,争辩道,“出题人的题不能算骗……毒瘤!……出题人的题,能算毒瘤骗钱题么?”接连便是难懂的话,什么“多叉splay随机点分治”,什么“树链剖分套分治FFT”之类,引得众人都哄笑起来:机房内外充满了快活的空气。
虽然他的题十分毒瘤,但他的题还总是有买家。李Fee现在有N道毒瘤题,想将这些题出成一组题来骗大钱。然而显而易见的是,一组题的毒瘤程度不仅和每道题的毒瘤程度有关,也跟它们的排列顺序有关,李Fee需要将它们排列成最毒瘤但又最能骗钱的那个顺序。
具体来说,这N道题每题都有一个毒瘤值,它们构成了一个序列。李Fee心目中有一个理想的毒瘤值序列,这个序列并不一定每一题的毒瘤值都是原本N道题中出现的,所以李Fee准备进行一些改动。这些改动体现在毒瘤值上就是将某道题的毒瘤值改为所有题的毒瘤值的二进制异或值。但是,改动题目是很麻烦的,他想算出最少需要多少次改动才能将原本的毒瘤值序列改成理想的毒瘤值序列,李Fee忙于出毒瘤题,他想请发明O(1/n)算法用暴力搜过所有毒瘤题的你帮他算出答案。但是他是个奸商,所以他并不打算给你报酬。
题解:
操作的本质其实就是一开始手里拿着a的异或和,每次选一个数和它换一下,不断这样下去
为了方便,我们把a的异或和和b的异或和分别放到队尾,排序一下判断这两个数列是否完全一样,如果不一样显然不存在一种方案,输出-1即可
把a,b离散化,a[i]和b[i]连边(包括a[n+1],b[n+1]),稍加分析会发现一次次的替换会形成环,最终会有很多个环。注意连边的时候我们以值域为点,这个值域出现的次数为点权,显然处理一个环的代价为这个环的点权之和。从一个环到另一个环的代价为1。
由于我们一开始拿着的是a的异或和,考虑a的异或和是否在环里,如果不在我们还需要花1的代价进入一个环(不在的情况并且仅当a[n+1]==b[n+1],和a数组之前有没有出现a[n+1]没有关系)
本质:对于一个环我们每次都可以花费1的代价让一个位置变的正确,处理完一个环我们就处理下一个
#include<algorithm> #include<cstring> #include<cstdio> #include<iostream> using namespace std; typedef long long ll; const int N=1e5+15; const int inf=1e9; int n; int fa[N],siz[N],vis[N],tot[N]; ll a[N],b[N],c[N],d[N]; inline ll read(){ char ch=getchar();ll s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } bool chk(){ sort(c+1,c+1+n);sort(d+1,d+1+n); for (int i=1;i<=n;i++) if (c[i]!=d[i]) return 0; return 1; } int find(int x){ if (fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } int main(){ freopen("duliu.in","r",stdin); freopen("duliu.out","w",stdout); n=read(); ll A=0,B=0; for (int i=1;i<=n;i++) a[i]=read(),A^=a[i]; for (int i=1;i<=n;i++) b[i]=read(),B^=b[i]; ++n; a[n]=A;b[n]=B; for (int i=1;i<=n;i++) c[i]=a[i],d[i]=b[i]; if (!chk()) {puts("-1");return 0;} int len=unique(c+1,c+1+n)-c-1; for (int i=1;i<=n;i++) a[i]=lower_bound(c+1,c+1+len,a[i])-c; for (int i=1;i<=n;i++) b[i]=lower_bound(c+1,c+1+len,b[i])-c; --n; for (int i=1;i<=n+1;i++) if (a[i]!=b[i]) tot[a[i]]++; for (int i=1;i<=n+1;i++) fa[i]=i,siz[i]=tot[i]; for (int i=1;i<=n+1;i++) if (a[i]!=b[i]){ int fx=find(a[i]),fy=find(b[i]); vis[a[i]]=1;vis[b[i]]=1; if (fx!=fy) { fa[fx]=fy; siz[fy]+=siz[fx]; } } //for (int i=1;i<=n+1;i++) printf("%d ",siz[i]); int cnt=0; for (int i=1;i<=n+1;i++) if (vis[i]&&find(i)==i) cnt+=siz[i]+1; --cnt; if (A!=B) --cnt;//开始的在连通块里 else ++cnt; printf("%d\n",cnt); return 0; }