题解:P12553 [UOI 2024] Lady's Gift
感觉比较有意思的一道所谓构造题。
考虑整个东西构成一个基环树森林,所以每个串最后都会走到环上,在串的一个后缀形成周期。
显然我们截取
考虑如下两个性质:
- 根据 WPL 推论,这个后缀的任意一个周期一定是最小周期的重复。而我们容易发现在此结论下一个环如果不是最小周期没有任何意义,所以我们一定只取最小周期。
- 不会有两个环是同构的,因为这样我们显然可以把环外挂的东西合并起来。
所以我们只需找出最小周期就能把串分到自己的连通块里。直接枚举最小周期在
然后考虑我们需要将最小周期循环同构的串塞到一起。这也可以使用哈希,对于每个新出现的周期,将它的所有循环同构串的哈希全部搞出来打上标记就可以了。
现在我们知道了每个串的起点所在的连通块和对应连通块环的形态。考虑我们显然希望一个串有尽可能长的后缀是在环上的,非要留出一段可以在环上的作为支链显然不优。
所以我们可以先对每个串找出第一个不能够在环上的位置,把剩余那个前缀挂在最后一个在环上的点上(当然如果整个串都在环上就直接挂在最后一个点上)。而由于这个串的每个后缀都必须找到对应走法的串,所以实际上我们是挂了一颗内向的字典树上去,字典树的每个节点都必须有至少一个串的起点对应到。
现在每个连通块形如一个基环字典树,我们的目标是所有节点上都有一个串的起点对应。据此我们可以分配掉未知的串,显然我们的构造是需要的数量的下界。
然后考虑如何构造方案。显然每个串的起点的字符就是它对应的点在字典树上表示的字符(或者,它父亲到它边上的字符);而每个串起点的点的指向可以考虑,所在的点在字典树上的部分显然是内向的,而环上的部分只需要和刚刚从环上退,找到第一个不在环上的位置的时候方向相同就可以了。
如果还有多出来的未知的串,自环即可。
时间复杂度
const int base=293;
const int mod1=200707201,mod2=998244353;
int bm1[N*3],bm2[N*3];
struct Hashing{
int val1,val2,len;
Hashing operator+(const Hashing p)const{
return {(1ll*val1*bm1[p.len]%mod1+p.val1)%mod1,
(1ll*val2*bm2[p.len]%mod2+p.val2)%mod2,
len+p.len
};
}
Hashing operator-(const Hashing p)const{//cut prefix out
return {(val1+mod1-1ll*p.val1*bm1[len-p.len]%mod1)%mod1,
(val2+mod2-1ll*p.val2*bm2[len-p.len]%mod2)%mod2,
len-p.len
};
}
bool operator==(const Hashing p)const{
return val1==p.val1&&val2==p.val2&&len==p.len;
}
bool operator<(const Hashing p)const{
if(val1==p.val1){
if(val2==p.val2) return len<p.len;
return val2<p.val2;
}
return val1<p.val1;
}
bool operator!=(const Hashing p)const{
return !((*this)==p);
}
};
int n;
string s[N];
Hashing pre[N*3];
string pat[N];
map <Hashing,pii> p;
vector <pii> trie[N][N];
vector <int> q;
int c[N],bol[N];
char tp[N],ans[N];
int r[N];
vector <int> cho[N];
int nxt[N];
int tot,ch[N][26];
int main(){
#ifdef Shun
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
ios::sync_with_stdio(false);
bm1[0]=bm2[0]=1;
fr1(i,1,6000) bm1[i]=1ll*bm1[i-1]*base%mod1,bm2[i]=1ll*bm2[i-1]*base%mod2;
cin>>n;
fr1(i,1,n) cin>>s[i],s[i]='@'+s[i];
fr1(i,1,n){
if(s[i][1]=='?'){
q.pb(i);
continue;
}
int cir=-1;
pre[0]={0,0,0};
fr1(j,1,3*n) pre[j]=pre[j-1]+(Hashing){s[i][j],s[i][j],1};
fr1(j,1,n){
for(int l=n+1;l<=n+n;l+=j){
if(pre[l+j-1]-pre[l-1]!=pre[n+j]-pre[n]) goto label;
}
cir=j;
break;
label:;
}
assert(~cir);
c[i]=cir;
Hashing lp={0,0,0};
fr1(j,n+1,n+cir) lp=lp+(Hashing){s[i][j],s[i][j],1};
fr1(j,n+1,n+cir){
if(p.count(lp)){
c[i]=0;
continue;
}
p[lp]={i,j-n};
lp=lp+(Hashing){s[i][j],s[i][j],1}-(Hashing){s[i][j],s[i][j],1};
}
int pos=p[lp].se;
fr2(j,n,1){
if(s[i][j]==s[i][j+cir]){
pos--;
if(!pos) pos=cir;
continue;
}
trie[p[lp].fi][pos].pb(mp(i,j));
goto label2;
}
trie[p[lp].fi][pos].pb(mp(i,0));
label2:;
}
fr1(i,1,n){
int maxn=0;
fr1(j,1,tot){
cho[j].clear();
fr1(k,0,25) ch[j][k]=0;
}
tot=c[i];
fr1(j,1,c[i]){
nxt[j]=j%c[i]+1;
tp[j]=s[i][n+j];
for(auto k:trie[i][j]){
string t=s[k.fi].substr(1,k.se);
reverse(t.begin(),t.end());
int pos=j;
for(auto p:t){
if(!ch[pos][p-'a']){
tot++;
tp[tot]=p;
ch[pos][p-'a']=tot;
nxt[tot]=pos;
}
pos=ch[pos][p-'a'];
}
cho[pos].pb(k.fi);
}
}
fr1(j,1,tot){
if(cho[j].empty()){
assert(!q.empty());
cho[j].pb(q.back());
q.pop_back();
}
}
fr1(j,1,tot){
for(auto k:cho[j]){
ans[k]=tp[j];
r[k]=cho[nxt[j]].back();
}
}
}
while(!q.empty()){
ans[q.back()]='z',r[q.back()]=q.back();
q.pop_back();
}
fr1(i,1,n) cout<<ans[i];
cout<<'\n';
fr1(i,1,n) cout<<r[i]<<' ';
ET;
}
//ALL FOR Zhang Junhao.