「ROI 2016 Day2」二指禅
考虑对于每个点,有前缀和后缀两种转移
对于两种转移分别建立( ext{trie})树,并且维护最小权值,对于(dp_i),可以匹配一段后缀从(j<i)得到
也可以匹配一段前缀更新(j>i),分别(O(n))枚举在两棵树上匹配即可完成转移
暴力转移复杂度为(O(n^2))
考虑优化转移效率,以(dp_i)匹配某一段前缀向(dp_j(j>i))转移为例
Part1 考虑求出最大的(j)
这个问题实际上就是求出每一个后缀的最大前缀匹配,也就是一个( ext{exkmp})问题
而且这题是一个多模板串版本,并不能用( ext{exkmp})解决
Solution 1
是把所有前缀hash值取出来,放到( ext{hash_table})里,然后二分长度+( ext{hash_table})查询,复杂度为(O(log L))
Solution 2
树剖+hash
对于( ext{trie})树树剖并且预处理hash值,重链上二分hash匹配,轻儿子暴力走,复杂度为(O(log ^2L))
如果使用全局平衡二叉树,可以做到单次查询复杂度为(O(log L)),常数应该远小于( ext{hash_table})
Part2 在(j_{max})基础上转移
考虑先求出最大的(j)之后,得到其对应的( ext{trie})树节点(u),在的祖先(u)中,每一种不同的权值更新一次
显然每一个祖先长度不同,因此最多只有(O(sqrt L))中不同的权值
因此可以for过去更新每一种权值,每次更新是一段区间,可以用树状数组维护
但是实际上极限情况下段极短,可以暴力枚举区间,所以并不是真的(O(sqrt Llog L))
比较容易说明复杂度的优化方法是:
采用分块(O(1))更新,(O(sqrt L))查询
这样可以做到稳定(O(sqrt L))完成转移
因此预处理复杂度为(O(mlog L-mlog ^2L)),转移复杂度为(O(msqrt L-msqrt Llog L))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
const int N=3e5+10,INF=1e9+10,D=6;
int n,m;
char T[N];
int Pow1[N],Pow2[N];
int S[N];
const ll K1=19260817,P1=114514;
const ll K2=1e9+13,P2=1919810;
struct Solver{
int S[N];
int trie[N][2],s[N],cnt,H1[N],H2[N];
int fa[N],top[N],sz[N],h1[N],h2[N],son[N],lst[N],bot[N],L[N],id[N],dfn,dep[N];
// lst[i] 记录祖先中第一个s[f]!=s[i]的节点
void dfs(int u){
sz[u]=1;
if(s[u]==s[fa[u]]) lst[u]=lst[fa[u]];
else lst[u]=fa[u];
rep(i,0,1) {
int v=trie[u][i];
if(!v) continue;
fa[v]=u,dep[v]=dep[u]+1;
h1[v]=(h1[u]*K1+i+1)%P1;
h2[v]=(h2[u]*K2+i+1)%P2;
dfs(v);
sz[u]+=sz[v];
if(son[u]==0 || sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs(int u,int t){
id[L[u]=++dfn]=u;
bot[top[u]=t]=u; if(son[u]) dfs(son[u],t);
for(int v:trie[u]) if(v&&v!=son[u]) dfs(v,v);
}
void Ins(char *T,int l,int w){
int now=0;
rep(i,1,l) {
int c=T[i]-'0';
if(!trie[now][c]) s[trie[now][c]=++cnt]=2e9;
cmin(s[now=trie[now][c]],w);
}
}
void Init(){
rep(i,1,n) {
H1[i]=(H1[i-1]*K1+S[i]+1)%P1;
H2[i]=(H2[i-1]*K2+S[i]+1)%P2;
}
dfs(0),dfs(0,0);
}
int Que(int i) {
// 求i 的最长匹配位置
int u=0;
while(i<=n) {
if(!trie[u][S[i]]) break;
if(trie[u][S[i]]!=son[u]){ u=trie[u][S[i++]]; continue; }
int l=1,r=min(n-i+1,dep[bot[top[u]]]-dep[u]),res=1;
while(l<=r){
int mid=(l+r)>>1;
if( (H1[i+mid-1]-1ll*H1[i-1]*Pow1[mid]%P1+P1)%P1 == (h1[id[L[u]+mid]]-1ll*h1[u]*Pow1[mid]%P1+P1)%P1 &&
(H2[i+mid-1]-1ll*H2[i-1]*Pow2[mid]%P2+P2)%P2 == (h2[id[L[u]+mid]]-1ll*h2[u]*Pow2[mid]%P2+P2)%P2)
l=mid+1,res=mid;
else r=mid-1;
}
i+=res,u=id[L[u]+res];
}
return u;
}
} X,Y;
struct BIT{
ll s[N];
void Init(){
memset(s,127,sizeof s);
}
void Add(int p,ll x){
while(p) cmin(s[p],x),p-=p&-p;
}
ll Que(int p){
ll res=1e18;
while(p<=n) cmin(res,s[p]),p+=p&-p;
return res;
}
} TX,TY;
ll dp[N];
int main(){
rep(i,Pow1[0]=Pow2[0]=1,N-1) {
Pow1[i]=Pow1[i-1]*K1%P1;
Pow2[i]=Pow2[i-1]*K2%P2;
}
scanf("%d%d%*d",&n,&m);
rep(i,1,n) scanf("%1d",S+i),X.S[i]=Y.S[n-i+1]=S[i];
rep(t,1,m) {
int w,l; scanf("%d%s",&w,T+1),l=strlen(T+1);
X.Ins(T,l,w),reverse(T+1,T+l+1),Y.Ins(T,l,w);
}
X.Init(),Y.Init();
rep(i,1,n) dp[i]=1e18;
TX.Init(),TY.Init();
TY.Add(1,0);
rep(i,0,n) {
// 暴力维护转移
if(i) {
cmin(dp[i],TX.Que(i));
int u=Y.Que(n-i+1);
while(u) {
int l=Y.dep[Y.lst[u]]+1,r=Y.dep[u];
if(r-l+1<=D) rep(j,l,r) cmin(dp[i],dp[i-j]+Y.s[u]);
else cmin(dp[i],TY.Que(i-r+1)+Y.s[u]);
u=Y.lst[u];
}
}
TY.Add(i+1,dp[i]);
if(i==n||dp[i]==1e18) continue;
int u=X.Que(i+1);
while(u) {
int l=X.dep[X.lst[u]]+1,r=X.dep[u];
if(r-l+1<=D) rep(j,l,r) cmin(dp[i+j],dp[i]+X.s[u]);
else TX.Add(i+r,dp[i]+X.s[u]);
u=X.lst[u];
}
}
printf("%lld
",dp[n]<1e18?dp[n]:-1);
}