• 『MdOI R2』Little Goth


    LuoguP6385 『MdOI R2』Little Goth

    link

    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)

    我们先证明:

    1. 若某个操作方案中途使得 (i=j),那么这个操作方案可以替换为初始通过操作使得 (i=j) 然后再执行本方案。

    (t=i-j),那么使得 (i=j) 等价于通过操作走完了 (t) 的长度,我们将这 (t) 步放在开头做对于答案没有影响。

    所以如果操作方案中出现了 (i=j) 的情况,那么等价于性质 (3) 中的第一类最优解。

    1. 若某个操作方案始终没有使得 (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 ;
    }
    
  • 相关阅读:
    中文分词算法工具hanlp源码解析
    Hanlp分词1.7版本在Spark中分布式使用记录
    Window离线环境下如何安装pyhanlp
    如何编译运行HanLP自然语言处理包
    函数调用面试题
    构造函数复习
    面向对象的好处
    递归实现查找页面所有节点
    面向对象和原型
    chrome浏览器调试工具的使用
  • 原文地址:https://www.cnblogs.com/Soulist/p/14272444.html
Copyright © 2020-2023  润新知