dsu:P3279 [SCOI2013] 密码
fish_love_cat · · 题解
马拉车是什么?我不知道啊?(天真)
来个纯粹并查集做法。
考虑这个玩意其实告诉了我们什么。
有两个区间对称位置相同,有两个点一定不同。
区间翻转一下拼在后面,然后我们强行令翻转前后对应位置相同,于是第一个要做的东西变成两个区间顺序比对依次相同。
萌萌哒 trick ST 表维护并查集即可。
于是我们就把一定相同的给合并了,剩下的是一定不同的边,注意到只有
我们把最前面的没填上字母且未染色的给贪心填上目前最优的字母,然后遍历所有互斥边给染色,整个连通块全部染色,然后直接按顺序做即可。非常暴力但是单次复杂度
找点可以用白雪皑皑 trick 并查集维护后继快速完成。
所有字母都来一次,算上前面 ST 表的复杂度,总体时间复杂度
并查集不能一遍写对的话调起来和吃史没有本质差别。
#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int fa[25][200005];
int find(int op,int x){
return fa[op][x]==x?x:fa[op][x]=find(op,fa[op][x]);
}
vector<int>ve[100005];
int n,m;
int rev(int x){
return n-x+1;
}
int nxt2[100005];
int find2(int x){
return nxt2[x]==x?x:nxt2[x]=find2(nxt2[x]);
}
int vis[100005],op;
int nxt[100005];
int find(int x){
if(vis[x]!=op)nxt[x]=nxt2[x],vis[x]=op;
return nxt[x]==x?x:nxt[x]=find(nxt[x]);
}
int ans[100005];
vector<int>c[100005];
signed main(){
cin>>m;
n=2*m;
for(int i=0;i<=20;i++)
for(int j=1;j<=n;j++)
fa[i][j]=j;
for(int i=m+1,j=m;i<=n;i++,j--)
fa[0][i]=j;
for(int i=1;i<=m;i++){
int x;
cin>>x;
x--;
ve[i-x/2-1].push_back(i+x/2+1);
ve[i+x/2+1].push_back(i-x/2-1);
if(!x)continue;
int l1,r1,l2,r2;
l1=i-x/2;
r1=i-1;
l2=rev(i+x/2);
r2=rev(i+1);
int siz=r1-l1+1;
for(int i=20;i>=0;i--)
if((1<<i)&siz){
fa[i][find(i,l1)]=find(i,l2);
l1+=1<<i;
l2+=1<<i;
}
}
for(int i=1;i<m;i++){
int x;
cin>>x;
ve[i-x/2].push_back(i+x/2+1);
ve[i+x/2+1].push_back(i-x/2);
if(!x)continue;
int l1,r1,l2,r2;
l1=i-x/2+1;
r1=i;
l2=rev(i+x/2);
r2=rev(i+1);
int siz=r1-l1+1;
for(int i=20;i>=0;i--)
if((1<<i)&siz){
fa[i][find(i,l1)]=find(i,l2);
l1+=1<<i;
l2+=1<<i;
}
}
for(int i=20;i;i--){
for(int j=1;j+(1<<i)-1<=n;j++)
fa[i-1][find(i-1,j)]=find(i-1,find(i,j)),
fa[i-1][find(i-1,j+(1<<(i-1)))]=find(i-1,find(i,find(i,j)+(1<<(i-1))));
}
n=m;
for(int i=1;i<=n;i++)
nxt2[i]=i,c[find(0,i)].push_back(i);
nxt2[n+1]=n+1;
for(int i=1;i<=n;i++){
if(ans[i]){
cout<<(char)(ans[i]+'a'-1);
continue;
}
op++;
int j=i;
while(j<=n){
int flc=find(0,j);
for(int k:c[flc]){
nxt2[find2(k)]=find2(k+1);
nxt[find(k)]=find(k+1);
ans[k]=op;
for(int qwq:ve[k]){
if(find(qwq)==qwq)
for(int kk:c[find(0,qwq)])
nxt[find(kk)]=find(kk+1);
}
}
j=find(j);
}
cout<<(char)(ans[i]+'a'-1);
}
return 0;
}
// 这个房间被打扫的一尘不染呢。
// 一定是维持着十年前的样子吧。
// 那又如何?
// 我也依然把被你们杀死的父亲的房间维持着原来的样子。
// 给这一切书上中止符吧,古拉纳特大人。
// 还是说,你想要双方继续流血和牺牲,继续让谁为此而悲痛欲绝呢?
// 我们的语言是共通的。
// 请务必给我们一个交谈的机会。
// ……带他们去客房。
// 让我考虑一下。
// …………
// 硫古纳大人,父亲是什么?
// 谁知道呢。
讲一个其他更快的写法。
首先你注意到这个东西把并查集建出来合并完以后,得到了若干互斥边,然后根据字典序是从前向后确定的。
我们直接把互斥边对于连通块的代表元建起来,然后依旧是顺序确定,每次确定的时候设置为所有相邻确定数字的 mex 即可。
这个玩意显然可以更快的维护,不用现在这么蠢的对每一个字符都扫一遍,查询 mex 可以用 bitset 或者 set,瓶颈反正是 ST 表复杂度
看完这段你就可以去写 AT_abc349_g 了。过了。
维护 mex 是不是也可以白雪皑皑,用并查集做来着。但我 perm 这么写挂飞了,饿啊。