LuoguP6385 『MdOI R2』Little Goth
EA 鸽鸽的神仙题,下面的题解基本上抄自官方题解。
设 (r) 为题设中满足条件的最大的 (r),首先 (j) 必然取 (r) 处,这是因为取 (j') 处那么存在一个方案为从 (r) 不断操作 (2) 得到 ((i,j')),容易发现两者代价相同。其次,我们知道答案的下界为 (L-j+|k-i|)
我们称操作 (3) 为传送,那么可以证明:
- 性质 (1) :若当前有 (i=j),那么我们会保持 (i=j)
注意到 (i=j) 时操作 (3) 的限制最松,其次,我们发现使得 (i e j) 的操作可以被使得 (i=j) 的操作替换掉(请注意我们时刻保持 (ile j))
- 性质 (2) :若当前有 (|i-j|=t),那么我们不会扩大 (|i-j|) 后进行传送。
注意到扩大后传送一定不会优于先传送后扩大(请注意字符串相等意味着子串也相同)
- 性质 (3) :最优解法有两种:一是先操作成 (i=j) 的情况,然后不断的通过操作 (1/2) 加部分传送到达终点;另一种是先操作成 (lle i<jle r),然后通过一次传送到达某个点,然后直接移动 (i) 走到 (x)
我们先证明:
- 若某个操作方案中途使得 (i=j),那么这个操作方案可以替换为初始通过操作使得 (i=j) 然后再执行本方案。
设 (t=i-j),那么使得 (i=j) 等价于通过操作走完了 (t) 的长度,我们将这 (t) 步放在开头做对于答案没有影响。
所以如果操作方案中出现了 (i=j) 的情况,那么等价于性质 (3) 中的第一类最优解。
- 若某个操作方案始终没有使得 (i=j),那么其至多传送一次。
根据性质 (2),最优解法一定不会扩大 (|i-j|),所以长度只能缩小无法增大。
现在考虑当前的 ((i,j)),假设先移动后再次进行传送,那么假设当前走到了 ((i',j')) 且 (|i'-j'|>1) 然后进行传送,我们可以证明这类情况不会发生。
先假设有 (i'>j),那么我们肯定可以通过更少的操作次数(即只操作 (i) )得到 ((j',j')) 这个二元组,容易发现这样更优,同时违背了前提,会被归类到 1 中。对于 (j'<i) 类似。
然后我们假设 (i'<j<j'),那么容易注意到这样的传送等价于先操作成 ((i',j)) 然后传送后再移动,容易发现等价。
于是我们假设 (i<i'<j'<j),那么这意味着传送的前提是不断的缩小初始字符区间的,从最初的字符集开始我们得到某个 ((i',j')) 并传送,那么此后肯定有字符串仍然是最初的字符串的子串,如果此后还进行传送,那么我们不如在初始状态缩小到足够小再传送,这样可以节约传送次数。
那么这样也至多传送一次,至此,如果中途出现了 (i=j) 那么性质 (3) 的第一部分即为最优解,否则性质 (3) 的第二部分为最优解。
求解
现在考虑对于两部分分别求解。
对于第一部分,问题等价于选择初始 ([i,j]) 中一个位置 (t),然后询问 (t o k) 的最短路。其他的贡献为常数。
我们发现问题等价于:(t) 可以左移,右移,每个位置有颜色,颜色相同可传送。
那么统计 (f_{i,j}) 表示颜色 (i) 走到位置 (j) 的最短路即可,可以通过 01 - BFS 在 (mathcal O(Sigma cdot n)) 的复杂度求解。
对于转移我们枚举这个第一次经过“传送”的颜色,假定最优解中途经过了颜色 (c),那么考虑 ((j-i)+f_{c,x}[xin [i,j]]+f_{c,k}+1) 即为答案。
假定 (c) 在 ([i,j]) 中出现过,那么答案为 (f_{c,x}[xin [i,j]]=1),否则答案取 (min(f_{c,i},f_{c,j})) 即可
对于第二部分,问题等价于对于 ((i,j)) 选择一个字串传送到一个 ([i',j']) 然后从 (i') 走到 (x) 并统计答案。
忽略掉常数,此时的代价为 (2i'-j')
- 观察:(i'ge k)
易知,我们需要使得 (j'ge k),否则等同于第一部分的情况,所以使得 (i'=k) 一定不劣。
我们有两类思路:
- 枚举 (i'),则 (j') 应尽可能大。
我们可以二分 (j') 并考虑判断 ([i',j']) 是否在 ([i,j]) 中出现过。
建立串 (S) 的后缀树,通过线段树合并维护 endpos 集,我们只需要定位 ([i',j']) 对应的节点,只需要判定 endpos 中是否存在区间 ([i,j-(j'-i')]) 内的数即可。
对于单个 (i') 进行确定的复杂度为 (mathcal O(log^2 n))
- 当区间长度被确定时,(i') 应尽可能小。
此时答案可以被描述为 (i'- extrm{len}+1)
建立原串 (S) 的后缀树,考虑树上每个节点,我们应该有此节点对应的字符串固定,此时我们希望求解其所有字串对应的最小的 (2L-R)
为此我们维护每个节点,对应的最小的 (i') 即可(可以考虑相同的边对应的 endpos 集是相同的)
我们考虑所有大于 (k) 的位置对应的字符串,我们希望维护仅考虑这些字符串,后缀树上每个字串对应的答案
我们先维护出节点 (u) 上对应的在 (k) 之后最小的 (i'),然后在后缀树上根据节点深度进行 dp,考虑 (f_i=min(f_{ extrm{father}_i},f_{ extrm{link}_i},i'- extrm{len}_i+1))
此时我们可以 (mathcal O(n)) 确定后缀树上每个节点对应的答案。
最后,我们使用两类算法来均摊复杂度以通过此题,离线所有查询,考虑每间隔 (B) 个位置就重新做一次上述 dp,对于查询的 (k),设上一次 dp 点为 (L),我们暴力枚举 ([k,L]) 中的所有 (i') 并采取 (mathcal O(log^2 n)) 的暴力来处理。
于是我们得到了一个 (mathcal O(frac{n^2}{B}+nBlog^2 n))(设 (n,q) 同阶)的算法。
选取合适的 (B) 可以做到 (mathcal O(nsqrt{n}log n))
如果对于第一部分有更高明的解决办法,可以做到 (mathcal O(nsqrt{nlog n}))
不过我挺好奇为啥理论上 (log^2) 的部分怎么跑得这么快。。。(mathcal O(n)) 的部分反而常数大一点?...
(Code:)
const int B = 80 ;
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int P = 998244353 ;
const int H = 2333 ;
const int N = 6e4 + 50 ;
const int inf = 1e9 ;
const int Inf = 1e8 ;
int n, m, tnt, head[N], vis[N], f[30][N], dis[N] ;
int bef[30][N], qAns[N] ;
long long fac[N], pre[N] ;
struct E {
int to, next, w ;
} e[N << 1] ;
void add(int x, int y, int z) {
e[++ tnt] = (E){ y, head[x], z }, head[x] = tnt ;
}
char s[N] ;
deque<int> q ;
void BFS(int x) {
rep( i, 1, n + 30 ) vis[i] = 0, dis[i] = inf ;
dis[x] = 0, q.push_back(x) ; int z = x - n ;
while(!q.empty()) {
int u = q.front() ; q.pop_front() ;
if(vis[u]) continue ; vis[u] = 1 ;
Next( i, u ) {
int v = e[i].to ;
if(dis[v] > dis[u] + e[i].w) {
dis[v] = dis[u] + e[i].w ;
if(dis[v] == dis[u]) q.push_front(v) ;
else q.push_back(v) ;
}
}
} rep( i, 1, n ) f[z][i] = dis[i] ;
}
int Get(int l, int r) { return (pre[r] - 1ll * pre[l - 1] * fac[r - l + 1] % P + P) % P ; }
struct node {
int l, r, id, cost, bef, wi, ans ;
} ; vector<node> p[N] ;
namespace S1 {
struct Suffix {
int ch[30] ;
int lef, lk, len ;
} t[N << 1] ;
struct Tr { int l, r, w ; } tr[N * 100] ;
vector<int> Go[N << 1] ;
int rem, bef = 1, cnt = 1, m, num, rt[N] ;
int fa[N][20], dep[N], Idnex[N] ;
int node( int l, int len ) {
t[++ cnt].lef = l, t[cnt].len = len, t[cnt].lk = 1 ;
return cnt ;
}
void insert( int x ) {
++ m, ++ rem ; int u = s[x], lst = 1 ;
while( rem ) {
while( rem > t[t[bef].ch[(int)s[m - rem + 1]]].len )
rem -= t[bef = t[bef].ch[(int)s[m - rem + 1]]].len ;
int &d = t[bef].ch[(int)s[m - rem + 1]], c = s[t[d].lef + rem - 1] ;
if( !d || u == c ) {
t[lst].lk = bef, lst = bef ;
if( !d ) d = node( m - rem + 1, inf ) ;
else break ;
}
else {
int np = node( t[d].lef, rem - 1 ) ;
t[np].ch[c] = d, t[np].ch[u] = node(m, inf) ;
t[d].lef += (rem - 1), t[d].len -= (rem - 1) ;
t[lst].lk = d = np, lst = np ;
} (bef == 1) ? -- rem : bef = t[bef].lk ;
}
}
void insert(int &x, int l, int r, int k) {
if(!x) x = ++ num ;
if(l == r) return ++ tr[x].w, void() ;
int mid = (l + r) >> 1 ;
if(k <= mid) insert(ls(x), l, mid, k) ;
else insert(rs(x), mid + 1, r, k) ;
tr[x].w = tr[ls(x)].w + tr[rs(x)].w ;
}
int Kth(int x, int l, int r, int k) {
if(!x || !k) return 0 ;
if(l == r) return tr[x].w ;
int mid = (l + r) >> 1 ;
if(k <= mid) return Kth(ls(x), l, mid, k) ;
else return Kth(rs(x), mid + 1, r, k) + tr[ls(x)].w ;
}
int qry(int x, int l, int r, int k) {
if(l == r) return l ;
int mid = (l + r) >> 1 ;
if(k <= tr[ls(x)].w) return qry(ls(x), l, mid, k) ;
else return qry(rs(x), mid + 1, r, k - tr[ls(x)].w) ;
}
int merge(int x, int u, int isr) {
int nw = (isr) ? x : ++ num ;
if(ls(x) && ls(u)) ls(nw) = merge(ls(x), ls(u), 0) ;
else ls(nw) = ls(x) + ls(u) ;
if(rs(x) && rs(u)) rs(nw) = merge(rs(x), rs(u), 0) ;
else rs(nw) = rs(x) + rs(u) ;
tr[nw].w = tr[ls(nw)].w + tr[rs(nw)].w ;
return nw ;
}
void dfs( int x, int Fa, int l ) {
if( t[x].len >= Inf ) t[x].len = n + 1 - t[x].lef ;
dep[x] = l + t[x].len, fa[x][0] = Fa ; int fl = 0 ;
rep( i, 1, 18 ) fa[x][i] = fa[fa[x][i - 1]][i - 1] ;
rep( i, 1, 27 ) if( t[x].ch[i] )
dfs( t[x].ch[i], x, l + t[x].len ), fl = 1,
Go[x].pb(t[x].ch[i]), merge(rt[x], rt[t[x].ch[i]], 1) ;
if( !fl ) {
int d = n - dep[x] + 1 ;
if(d <= n) Idnex[d] = x, insert(rt[x], 1, n, d) ;
}
}
int Get(int l, int r) {
int u = Idnex[l], le = r - l + 1 ;
drep( i, 0, 17 ) if(dep[fa[u][i]] >= le) u = fa[u][i] ;
return u ;
}
int Id[N], g[N], F[N], D[N] ;
bool cmp(int x, int y) { return dep[x] < dep[y] ; }
void Dfs(int x) {
for(int v : Go[x])
Dfs(v), g[x] = min(g[x], g[v]) ;
}
void build(int p) {
rep( i, 1, cnt ) D[i] = F[i] = g[i] = inf ;
rep( i, p, n ) g[Idnex[i]] = i ;
Dfs(1), D[0] = F[0] = inf ; int u ;
rep( i, 3, cnt )
u = Id[i],
F[u] = min(D[t[u].lk], D[fa[u][0]]),
D[u] = min(F[u], g[u] - dep[u] + 1) ;
}
bool check(int l1, int r1, int l2, int r2) {
if(r1 - l1 > r2 - l2) return 0 ;
int len = r1 - l1, ed = r2 - len, u = Get(l1, r1) ;
int l = Kth(rt[u], 1, n, l2 - 1), sz = tr[rt[u]].w ;
if(l == sz) return 0 ;
return (qry(rt[u], 1, n, l + 1) <= ed) ;
}
int Get(int p, int L, int R) {
int l = p, r = n, ans = inf ;
while(l <= r) {
int mid = (l + r) >> 1 ;
if(check(p, mid, L, R)) ans = min(ans, 2 * p - mid), l = mid + 1 ;
else r = mid - 1 ;
} return ans ;
}
void solve() {
s[++ n] = 27, t[0].len = inf ;
rep( i, 1, n ) insert(i) ; -- n ;
rep( i, 1, cnt ) rt[i] = ++ num ;
dfs(1, 1, 0) ; rep( i, 1, cnt ) Id[i] = i ;
rep( i, 1, n ) t[Idnex[i]].lk = Idnex[i + 1] ;
sort(Id + 1, Id + cnt + 1, cmp) ;
int last = n ; build(n) ;
for(re int i = n; i >= 1; -- i) {
if(i % B == 0) last = i, build(i) ;
for(auto &v : p[i]) {
int u = Get(v.l, v.r), len = v.r - v.l + 1 ;
v.ans = min(v.ans, min(F[u], g[u] - len + 1)) ;
}
for(re int j = i; j <= last; ++ j)
for(auto &v : p[i]) v.ans = min(v.ans, Get(j, v.l, v.r)) ;
}
rep( i, 1, n ) for(auto &v : p[i])
v.ans += v.wi, qAns[v.id] = v.cost + min(v.ans, v.bef) ;
}
}
signed main()
{
n = gi(), m = gi(), scanf("%s", s + 1) ;
rep( i, 1, n ) s[i] -= ('a' - 1), add(i, s[i] + n, 1), add(s[i] + n, i, 0) ;
rep( i, 1, n - 1 ) add(i, i + 1, 1), add(i + 1, i, 1) ;
rep( i, 1, 26 ) BFS(i + n) ; fac[0] = 1 ;
rep( i, 1, n ) pre[i] = (1ll * pre[i - 1] * H + s[i]) % P ;
rep( i, 1, n ) fac[i] = 1ll * fac[i - 1] * H % P ;
rep( i, 1, n ) {
rep( j, 1, 26 ) bef[j][i] = bef[j][i - 1] ;
++ bef[(int)s[i]][i] ;
}
rep( j, 1, m ) {
int x = gi(), y = gi(), k = gi() ;
int len = n - max(x, y) ;
int l = 0, r = len, ans = 0 ;
while(l <= r) {
int mid = (l + r) >> 1 ;
if(Get(x, x + mid) == Get(y, y + mid)) ans = mid, l = mid + 1 ;
else r = mid - 1 ;
}
int L = x, R = x + ans, Ans = abs(L - k) ;
rep( i, 1, 26 )
if(bef[i][R] - bef[i][L - 1]) Ans = min(Ans, R - L + f[i][k] + 1) ;
else Ans = min(Ans, R - L + min(f[i][L], f[i][R]) + f[i][k] + 1) ;
p[k].pb((node){ L, R, j, n - R, Ans, R - L - k + 1, inf }) ;
}
S1::solve() ;
rep( i, 1, m ) printf("%d
", qAns[i] ) ;
return 0 ;
}