[NEERC2004-2005] Hyper Almost Permutative String 题解

发布时间 2023-10-24 16:26:10作者: AFewSuns

题目链接

题目大意

称一个长度为 \(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\) 所在环一样)。

  1. \(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\) 具体在哪无关。

  1. \(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\) 具体在哪无关。

  1. \(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\) 具体在哪无关。

  1. \(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);
	}
}