[NEERC2004-2005] Hyper Almost Permutative String 题解
题目大意
称一个长度为 \(n\) 的字符串为排列的,当且仅当它包含了前 \(n\) 个大写字母。
称一个长度为 \(n+1\) 的字符串为基本排列的,当且仅当可以通过删去一个字符使得它是排列的。
现在给定两个长为 \(n\) 的排列的字符串 \(s_1,s_2\),求一个最短的字符串 \(S\),使得 \(s_1,s_2\) 均为其子串,且 \(S\) 任意一个长为 \(n+1\) 的子串都是基本排列的。
\(n \leq 26\)。
题目分析
手玩一下 \(S\) 会长什么样,首先它的开头肯定是 \(s_1\),结尾是 \(s_2\)。然后选 \(>n\) 的字符肯定不优。
由于 \(s_1\) 是排列的,所以 \(S_{n+1}\) 可以随便填。而 \(S_{n+2}\) 就不太行了,因为要求 \(S_{2\sim n+2}\) 为基本排列的,少了第一个字符,所以 \(S_{n+1}\) 和 \(S_{n+2}\) 间一定要有一个等于 \(S_1\)。
同理,\(S_{n+1},S_{n+2},S_{n+3}\) 间一定要有一个等于 \(S_2\),以此类推。考虑一些简单的情况,比如 \(S_{n+1}\) 是个其他东西,那么就确定了 \(S_{n+2}=S_1,S_{n+3}=S_2,\cdots\),直到下一个需要的字符就是 \(S_{n+1}\)。
举个例子,假如 \(s_1=\texttt{ABCDEFG},S_{n+1}=\texttt{E}\),那么 \(S\) 的前几位就确定了:\(\texttt{ABCDEFGEABCD}\)。
发现很有规律!相当于选中间某个字符填在 \(S_{n+1}\),然后把这个字符前面的字符按顺序接在后面。同时注意到这中间所有长为 \(n\) 的子串都是非排列的(出现了两个相同字符),而最后 \(n\) 个字符则是排列的。
于是就可以抛开 \(S\) 了!考虑直接在 \(s_1\) 上刻画这个操作,相当于选择中间某个字符,把两边交换,代价为选择的这个字符所在位置。问 \(s_1\) 操作到 \(s_2\) 的最小代价。
这是个很套路的东西,考虑将首尾加一个 \(0\) 连成环,那么操作变成选择某个字符与 \(0\) 交换,代价也很好刻画,最终得到的字符串就是从 \(0\) 开始顺时针走得到的字符串。
枚举最后的 \(0\) 跑到了哪里,那么相当于有两个排列,每次可以交换某个数和 \(0\),代价为 \(0\) 顺时针到这个数的距离,求第一个排列变成第二个排列的最小代价。
如果没有这个代价,显然就是一个个把置换环换了就行了。
如果只有一个环,那么直接用 \(0\) 按顺序转一圈就行了,这是下界。具体来说,用 \(0\) 交换某个数大概可以看成这个数逆时针走了若干步,走了多少步代价就是多少。而每个数走到对应的位置至少也要走这么多步,用 \(0\) 一个个换则顶到了这个下界。
如果有多个环,那么 \(0\) 只能交换到其他环上转,这会产生多余的代价,使得策略不好确定。
现在单独考虑一个没有 \(0\) 的环,且目标只是把这个环复原(即不管其他环),所需的最小代价。
假设 \(0\) 所在位置为 \(pos\),初始与 \(i\) 交换,且 \(i\) 对应的位置为 \(p_i\)。设 \(w\) 为如果 \(0\) 一开始就在这个环上,顺序绕一圈回到原点的代价总和。注意这里走的顺序为 \(p_i \to i\)(与 \(0\) 所在环一样)。
- \(pos<i<p_i\)(\(i<p_i<pos\) 同理)
\(0\) 走的顺序为 \(pos \to i \to \cdots \to p_i \to pos\)。
记 \(len=p_i-i\),中间 \(i\) 走到 \(p_i\) 的代价则为 \(w-(n+1-len)\),即少了从 \(p_i\) 走回 \(i\) 的代价。
而首尾多出来的代价恰好为 \(n+1-len\),所以总的代价为 \(w\),定值,与 \(pos\) 具体在哪无关。
- \(i<pos<p_i\)
\(0\) 走的顺序为 \(pos \to i \to \cdots \to p_i \to pos\)。
记 \(len=p_i-i\),中间 \(i\) 走到 \(p_i\) 的代价则为 \(w-(n+1-len)\),即少了从 \(p_i\) 走回 \(i\) 的代价。
而首尾多出来的代价为 \(2(n+1)-len\),所以总的代价为 \(w+n+1\),定值,与 \(pos\) 具体在哪无关。
- \(pos<p_i<i\)(\(p_i<i<pos\) 同理)
\(0\) 走的顺序为 \(pos \to i \to \cdots \to p_i \to pos\)。
记 \(len=p_i-i\),中间 \(i\) 走到 \(p_i\) 的代价则为 \(w-len\),即少了从 \(p_i\) 走回 \(i\) 的代价。
而首尾多出来的代价为 \(n+1+len\),所以总的代价为 \(w+n+1\),定值,与 \(pos\) 具体在哪无关。
- \(p_i<pos<i\)
\(0\) 走的顺序为 \(pos \to i \to \cdots \to p_i \to pos\)。
记 \(len=p_i-i\),中间 \(i\) 走到 \(p_i\) 的代价则为 \(w-len\),即少了从 \(p_i\) 走回 \(i\) 的代价。
而首尾多出来的代价恰好为 \(len\),所以总的代价为 \(w\),定值,与 \(pos\) 具体在哪无关。
代价都与 \(pos\) 无关,这是好的。\(2,3\) 看起来就不优,考虑证明一定存在 \(1,4\) 两种情况之一。
反证法。我们称 \(i<p_i\) 时 \(i \to p_i\) 为往右的边,反之为往左的边。因为只存在 \(2,3\) 情况,所以对于环上所有往右的边,一定跨过 \(pos\)。
任取一条,假设为 \(x \to p_x\)。由于所有往左的边一定不能跨过 \(pos\),所以这个环走到 \(p_x\) 之后就回不去 \(x\) 了,与环矛盾。
所以对于每个 \(pos\),一定存在 \(1,4\) 两种情况之一,代价都为 \(w\)。这说明 \(0\) 一开始在哪里并不重要!
于是只需要先将 \(0\) 所在环换了,再一个个换其他环即可。时间复杂度 \(\mathcal O(n^2)\)。
注意要把 \(s_2 \to s_1\) 的情况也计算一遍,取最小值。
代码
#include<bits/stdc++.h>
using namespace std;
using namespace my_std;
vector<ll> vec;
ll t,n,a[33],b[33],mp[33],p[33],id[33],cnt=0;
bl ck[33];
char s[33];
il ll calc(ll x,ll y){
ll tmp=(y-x+n+1)%(n+1);
vec.push_back(tmp);
return tmp;
}
il ll solve(){
vec.clear();
ll res=0;
fr(i,0,n) mp[b[i]]=i;
fr(i,0,n) p[i]=mp[a[i]];
fr(i,0,n) ck[i]=0;
fr(i,0,n){
if(ck[i]) continue;
if(p[i]==i){
ck[i]=1;
continue;
}
cnt=0;
id[++cnt]=i;
for(ll x=p[i];x!=i;x=p[x]) id[++cnt]=x;
fr(j,1,cnt) ck[id[j]]=1;
if(!i){
reverse(id+2,id+cnt+1);
fr(j,2,cnt) res+=calc(id[j-1],id[j]);
}
else{
id[cnt+1]=id[1];
ll pos=0;
fr(j,1,cnt) if((id[j]<id[j+1]&&(p[0]<id[j]||p[0]>id[j+1]))||(id[j+1]<p[0]&&p[0]<id[j])) pos=j;
rotate(id+1,id+pos,id+cnt+1);
reverse(id+2,id+cnt+1);
res+=calc(p[0],id[1]);
fr(j,2,cnt) res+=calc(id[j-1],id[j]);
res+=calc(id[cnt],p[0]);
}
}
return res;
}
il void work(ll pos){
pc('A'+a[pos]-1);
fr(i,1,pos-1) pc('A'+a[i]-1);
fr(i,pos+1,n) b[i-pos]=a[i];
b[n-pos+1]=a[pos];
fr(i,1,pos-1) b[n-pos+1+i]=a[i];
fr(i,1,n) a[i]=b[i];
}
int main(){
n=read();
a[0]=b[0]=0;
scanf("%s",s+1);
fr(i,1,n) a[i]=s[i]-'A'+1;
scanf("%s",s+1);
fr(i,1,n) b[i]=s[i]-'A'+1;
ll pos1=0,ans1=inf,pos2=0,ans2=inf;
fr(i,0,n){
ll tmp=solve();
if(tmp<ans1){
ans1=tmp;
pos1=i;
}
rotate(b,b+1,b+n+1);
}
swap(a,b);
fr(i,0,n){
ll tmp=solve();
if(tmp<ans2){
ans2=tmp;
pos2=i;
}
rotate(b,b+1,b+n+1);
}
if(ans1<ans2) swap(a,b);
else{
ans1=ans2;
pos1=pos2;
}
rotate(b,b+pos1,b+n+1);
solve();
fr(i,1,n) pc('A'+a[i]-1);
fr(i,0,(ll)vec.size()-1){
ll now=vec[i];
work(now);
}
}