题解 P6396 【要有光】
真是神题
题意简述
好一道翻译题
一个字符串 \(S_0\) ,并令 \(S=S_0\) ,对 \(S\) 进行若干操作。
有五种操作:
- 光归:若 \(S\) 非空,可将 \(S\) 变为 \(S\) 的最长回文后缀,代价 \(A\)。
- 光辉:若 \(S\) 为回文串,且存在 \(T\) (可为空串)为 \(S_0\) 的子串,\(S\) 为 \(T\) 的最长回文后缀,可将 \(S\) 变为 \(T\) ,代价 \(B\)。
- 光隐:若 \(S\) 为非空回文串,可删去其长度相等且长度不大于 \(k\) 的前缀与后缀(删去后不能为空串),代价 \(C\)。
- 光腾:若 \(S\) 为非空回文串,且在 \(S\) 两端加上一对反串后生成的新回文串 \(T\) 是 \(S_0\) 的子串,可将 \(S\) 变为 \(T\) ,代价 \(D\)。
- 光戈:若 \(S\) 非空,可在 \(S\) 前加入任意字符,代价 \(E\)。使用该操作后不能再进行其它操作。
\(q\) 次询问,求将 \(S\) 变为 \(S_0[l,r]\) 的最小代价。
\(1 \leq q,|S| \leq 10^5\),\(1 \leq k \leq 5\),\(\sum \leq 52\)
题目分析
主题是回文串,当然得在回文自动机上处理。
考虑在回文自动机的节点间建单向边,然后跑单源最短路。
记边为 \((u,v,w)\)。
- 光归:\((i,fail_i,A)\),\(fail_i\) 可以为 \(0\)。
- 光辉:\((fail_i,i,B)\),\(fail_i\) 可以为 \(0\)。
- 光隐:\((i,pth-fa_i,C)\),\(p=1,2,\cdots,k\)。\(pth-fa_i\) 表示 \(p\) 级祖先,不能为 \(0\)。
- 光腾:将 \(i\) 向 \(fail\) 树中它的子树中所有节点连边 \((i,son,D)\)。
这样会连出 \(O(|S|^2)\) 条边,不行。
可以对每个点 \(i\) 建一个虚点 \(i'\),将 \(i'\) 建按照 \(fail\) 树连边,边权为 \(0\)。注意:\(i\) 并没有按照 \(fail\) 树连边。
然后对于每个点,连边 \((i',i,0)\) ,这样虚点可以随意到达它;连边 \((i,i',D)\) ,这样它就可以花费 \(D\) 的代价到达虚点,从而到达子树中的所有点。
光戈操作较为特殊,只要使用代价就已确定,故在询问时再考虑。
跑一遍 Dijkstra
,得到 \(dis(i)\) ,表示到达 \(i\) 状态最小的代价。
注意有个小细节:若 \(S_0\) 本身不是回文串,需要先进行一次光归将其变到 PAM
的节点上,因为此时不能进行其它操作。
接下来考虑询问。首先如果询问是 \(S_0[1,n]\) 需要特判,答案为 \(0\)。
由于光戈操作只能由回文串在前面添加字符而来,故开始光戈前一定是询问串的一个回文后缀。
可以从询问串的最长后缀跳 \(fail\),假设跳到了节点 \(p\) 且满足 \(len_p \leq r-l+1\)。
暴力跳 \(fail\) 仍然是不可取的 虽然能水到 85 pts ,会被链的情况卡到 \(O(|S|^2)\)。
但我们发现跳 \(fail\) 时 \(len\) 是在不断减小的,也就是查询的实际上是从根出发的一条链的最小值。
那倍增出第一个 \(len_p \leq r-l+1\),记由根出发(不包括 0 号节点)到 \(u\) 节点的链上, \(g_u=dis(p)-len_pE\) ,答案即为
时间复杂度 \(O(k|S|+(k|S|+q)logn)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const long long N=4e5+5;
long long m,k,A,B,C,D,E;
struct nodd{
long long to,nxt;
long long w;
}e[N*2];
long long head[N],cntt;
void add(long long u,long long v,long long w){
e[++cntt].to=v;
e[cntt].w=w;
e[cntt].nxt=head[u];
head[u]=cntt;
}
//建图
struct nod{
int ch[52];
int len,fail,fa;
nod(){
memset(ch,0,sizeof(ch));
len=fail=0;
}
}t[N>>1];
char s[N];
long long lst=0,cnt=1;
long long trans(char c){
if(c>='a' && c<='z') return c-'a';
else return c-'A'+26;
}
//转换字符
void PAM(long long n){
long long p=lst;
long long c=trans(s[n]);
while(s[n-t[p].len-1]!=s[n]) p=t[p].fail;
if(!t[p].ch[c]){
cnt++;
long long q=t[p].fail;
while(s[n-t[q].len-1]!=s[n]) q=t[q].fail;
t[cnt].fail=t[q].ch[c];
t[cnt].len=t[p].len+2;
t[p].ch[c]=cnt;
t[cnt].fa=p;
}
lst=t[p].ch[c];
}
struct abc{
long long num,dis;
};
long long dis[N];
bool vis[N];
bool operator <(abc x,abc y){
return x.dis>y.dis;
}
priority_queue <abc> q;
long long pre[N],ex;
long long f[N][19],g[N];
//g: 根节点(不含0)到此的 dis[p]-t[p].len*E 最小值
void dfs(long long u){
for(long long i=head[u];i;i=e[i].nxt){
long long v=e[i].to;
f[v][0]=u;
g[v]=min(g[u],dis[v]-t[v].len*E);
dfs(v);
}
}
//预处理 f和 g
int main(){
scanf("%s",s+1);
long long n=strlen(s+1);
cin>>k>>A>>B>>C>>D>>E;
t[0].fail=t[1].fail=1;
t[1].len=-1;
for(long long i=1;i<=n;i++) PAM(i),pre[i]=lst;
//PAM
if(t[lst].len!=n) ex=A;
for(long long i=2;i<=cnt;i++)
add(i,t[i].fail,A);
for(long long i=2;i<=cnt;i++)
add(t[i].fail,i,B);
for(long long i=2;i<=cnt;i++){
long long x=t[i].fa;
for(long long j=1;x && j<=k;x=t[x].fa,j++)
add(i,x,C);
}
for(long long i=2;i<=cnt;i++)
add(i+cnt,i,0);
for(long long i=2;i<=cnt;i++)
add(i,i+cnt,D);
for(long long i=2;i<=cnt;i++)
if(t[i].fa>1)
add(t[i].fa+cnt,i+cnt,0);
//虚点
//建图
memset(dis,0x7f,sizeof(dis));
dis[lst]=0;
q.push((abc){lst,0});
while(!q.empty()){
abc tmp=q.top();
q.pop();
long long u=tmp.num;
if(vis[u]) continue;
vis[u]=1;
for(long long i=head[u];i;i=e[i].nxt){
long long v=e[i].to;
if(vis[v]) continue;
if(dis[u]+e[i].w<dis[v]) dis[v]=dis[u]+e[i].w;
q.push((abc){v,dis[v]});
}
}
//Dijkstra
memset(head,0,sizeof(head));
cntt=0;
for(long long i=2;i<=cnt;i++)
add(t[i].fail,i,0);
g[0]=1e18;
dfs(0);
for(long long j=1;j<=18;j++)
for(long long i=0;i<=cnt;i++)
f[i][j]=f[f[i][j-1]][j-1];
cin>>m;
for(long long i=1;i<=m;i++){
long long l,r;
scanf("%lld%lld",&l,&r);
if(l==1 && r==n){
puts("0");
continue;
}
//特判
long long p=pre[r];
if(t[p].len>r-l+1){
for(long long j=18;j>=0;j--)
if(t[f[p][j]].len>r-l+1) p=f[p][j];
p=f[p][0];
}
//倍增
printf("%lld\n",(r-l+1)*E+g[p]+ex);
}
}