Description
给定一个只包含小写字母的字符串S
定义一个只包含小写字母和数字的字符串T是好的,当且仅当,|T|=k,且将T中的‘0’变成‘o’,‘1’变成‘i’,‘3’变成‘e’,4变成‘a’,5变成‘s’,7变成‘t’,8变成‘b’,9变成‘g’之后是S的子串
求一个长度最小的字符串ST,满足所有好的字符串T都是ST的子串
|S|<=5000,k<=500,k<=|S|/2
Solution
首先,我们将所有长度为k-1的字符串看做点,所有长度为k的字符串看做边,那么问题变成了找一条最短的路径,包含所有关键边。
注意到所有的关键边一定都在一个连通块内,我们只保留所有关键边,问题变成了加上最小的边使得这张图有欧拉路径
定义一个点的度数为入度-出度,换句话说,加上最少的边,使得最多一个点度数为1,最多一个点度数为-1
如果我们把所有度数为正的点放到二分图的左边,度数为负的点放到二分图的右边,这似乎变成了一个匹配问题
我们现在来证明在这个匹配问题上贪心的正确性:
即我们每次直接找权值最小的一条边匹配
设当前权值最小的边为(u,x),权为dis(u,x)
考虑反证,最终解选择了(u,y),(v,x)作为匹配,我们要说明dis(u,x)+dis(v,y)<=dis(u,y)+dis(v,x)
设A(u,x)表示最长的一个L,满足u的长度为L的后缀=x的长度为L的前缀,我们可以知道dis(u,x)=k-1-L
那么我们就要证A(u,x)+A(v,y)>=A(u,y)+A(v,x)
因为A(u,x)是最大的,所以A(u,y)是A(u,x)的前缀,A(v,x)是A(u,x)的后缀
如果v和y没有相交,那么显然成立
否则可以取等号
画一画就知道了
那么我们直接贪心就好了,但是点数可能很多?
注意到如果两个字符串将数字换成字母之后是相同的那么出现次数也相同
可以一起做
具体做法我们将所有度数<0的点扔到一个可重集S中,所有度数>0的点扔到一个可重集T中
每次删去S中所有点的第一个字符,T中所有点的最后一个字符,然后将所有S和T相同的元素去掉当做匹配,匹配的代价就是删去的字符数量
注意我们找的是欧拉路径所以最后一次匹配的代价要删掉
Code
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef unsigned long long ll;
const int N=5e3+5;
const ll p=29;
int n,k,tot,deg[N<<1],s[N],t[N];
char st[N];
ll ha[N],pw[N],sum[N],pw2[N];
map<ll,int> id;
map<ll,ll> S,T;
typedef map<ll,ll> :: iterator it;
ll hash(int l,int r) {return ha[r]-ha[l-1]*pw[r-l+1];}
bool ok(char c) {
if (c=='o') return 1;if (c=='i') return 1;
if (c=='e') return 1;if (c=='a') return 1;
if (c=='s') return 1;if (c=='t') return 1;
if (c=='b') return 1;if (c=='g') return 1;
return 0;
}
ll calc(int l,int r) {return pw2[sum[r]-sum[l-1]];}
int main() {
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d",&k);scanf("%s",st+1);n=strlen(st+1);
pw[0]=1;fo(i,1,n) pw[i]=pw[i-1]*p;
pw2[0]=1;fo(i,1,100) pw2[i]=pw2[i-1]<<1;
fo(i,1,n) ha[i]=ha[i-1]*p+st[i]-'a'+1;
fo(i,1,n) sum[i]=sum[i-1]+ok(st[i]);
ll ans=k-1;
fo(i,1,n-k+1) {
ll h=hash(i,i+k-1);
if (!id[h]) {
id[h]=++tot;ans+=calc(i,i+k-1);
h=hash(i,i+k-2);if (!id[h]) id[h]=++tot;
deg[id[h]]++;if (ok(st[i+k-1])) deg[id[h]]++;
h=hash(i+1,i+k-1);if (!id[h]) id[h]=++tot;
deg[id[h]]--;if (ok(st[i])) deg[id[h]]--;
}
}
fo(i,1,n-k+2) {
ll h=hash(i,i+k-2);
if (deg[id[h]]<0) {
s[++s[0]]=i;
S[h]=-deg[id[h]];
deg[id[h]]=0;
}
}
fo(i,1,n-k+2) {
ll h=hash(i,i+k-2);
if (deg[id[h]]>0) {
t[++t[0]]=i;
T[h]=deg[id[h]];
deg[id[h]]=0;
}
}
int lst=0,cnt=0,len=k-1;
while (len) {
fo(i,1,s[0]) {
ll h=hash(s[i],s[i]+len-1);
ll hh=hash(s[i]+1,s[i]+len-1);
S[hh]=S[hh]+S[h]*(ok(st[s[i]])+1);
S.erase(h);
}
fo(i,1,t[0]) {
ll h=hash(t[i],t[i]+len-1);
ll hh=hash(t[i],t[i]+len-2);
T[hh]=T[hh]+T[h]*(ok(st[t[i]+len-1])+1);
T.erase(h);
}
fo(i,1,s[0]) s[i]++;len--;
fo(i,1,s[0]) {
ll h=hash(s[i],s[i]+len-1);
ll d=min(S[h],T[h]);
if (d) {
ans+=calc(s[i],s[i]+len-1)*d*(k-1-len);
S[h]-=d;T[h]-=d;
lst=k-1-len;
if (!S[h]) S.erase(h);
if (!T[h]) T.erase(h);
}
}
tot=0;fo(i,1,s[0]) if (S[hash(s[i],s[i]+len-1)]) s[++tot]=s[i];s[0]=tot;
tot=0;fo(i,1,t[0]) if (T[hash(t[i],t[i]+len-1)]) t[++tot]=t[i];t[0]=tot;
}
printf("%lld\n",ans-lst);
return 0;
}