zoukankan      html  css  js  c++  java
  • Codeforces 1340F: Nastya and CBS

    题目传送门:1340F

    题意简述

    你需要动态维护一个多种括号组成的括号序列。需要支持两种操作:

    1. 修改单一位置的括号。
    2. 查询一段区间是否是一个合法的括号序列。

    序列长度为 (n),不同的括号种类数为 (k),操作次数为 (q)

    一个多种括号组成的括号序列 (S) 是合法的当且仅当:

    1. (S) 为空。
    2. (S) 可以表示为 (A + B) 的形式,其中 (A, B) 都是合法的。
    3. (S) 可以表示为 ( exttt{(}^ast + A + exttt{)}^ast) 的形式,其中 (A) 是合法的并且 ( exttt{(}^ast)( exttt{)}^ast) 是一对相同种类的左右括号

    例如 ()[{}()]{([{}])} 是合法的,而 ][][([{)]} 不是。

    题解

    部分思路来自于 EntropyIncreaser。

    我们考虑使用线段树维护,遇到的问题有二:

    1. 线段树中的每个节点应该存储什么信息?
    2. 如何合并信息?

    考虑一段括号序列,在不断删去相邻的匹配括号之后,得到的结果。如果其中仍然包含了相向的括号(此时必然不匹配,因为如果匹配就会被删去),例如 (],则在它两边再拼接上任何括号序列,最终都一定不会变成合法的括号序列。

    如果已经可以确保区间内不可能变成合法的括号序列(也就是上述情况发生了),就可以打一个不可行的标记。

    否则得到的结果一定是若干个右括号,加上若干个左括号(都可以为 (0) 个),例如 )]}[((

    可以发现只要在两边加上合适的括号,就可以把括号全部消除,变成合法的括号序列。

    所以要记录的信息就是它,就是消到不能消之后的括号序列,分为「在左侧的右括号」和「在右侧的左括号」两部分。

    先不考虑如何存储的问题,着眼于区间信息的合并上,如何合并两个子区间的信息呢?

    我们令左子区间的左侧部分(右括号)为 (ll),左子区间的右侧部分(左括号)为 (lr)
     再令右子区间的左侧部分(右括号)为 (rl),右子区间的右侧部分(左括号)为 (rr),接下来分两种情况:

    1. 如果 (lr)(rl) 短。
      例如 )[( + )]}{( 这种情况,合并后变成 )}{(
      可以发现必须有:整个 (lr) 要匹配得上 (rl) 的长度为 (|lr|) 的前缀。
      匹配上之后,再把 (rl) 的去掉这个前缀的部分连接在 (ll) 的后面,作为新的左侧部分,而右侧部分就是 (rr)
      如果匹配不上,那就打上无解的标记。
    2. 如果 (lr)(rl) 长。
      例如 )[( + ){( 这种情况,合并后变成 )[{(
      可以发现必须有:整个 (rl) 要匹配得上 (lr) 的长度为 (|rl|) 的后缀。
      匹配上之后,再把 (lr) 的去掉这个后缀的部分连接在 (rr) 的前面,作为新的右侧部分,而左侧部分就是 (ll)
      如果匹配不上,那就打上无解的标记。

    官方题解中使用了可持久化平衡树 + 哈希来维护信息,显然是可行的,而且时空复杂度都是 (mathcal O (n log^2 n)) 的。

    实际上存在一种不那么麻烦,而且空间复杂度为线性的做法:

    直接仅存储两部分的长度和字符串哈希值,不需要使用平衡树。

    当合并的时候,需要查询某个节点左/右侧部分的某个长度的前/后缀的哈希值,和将要匹配的串进行比对。

    但是已知整个串的哈希值是无法求出前/后缀的哈希值的,我们考虑递归!

    实现两个函数来求某个节点左/右侧部分的前/后缀哈希值(详见代码中的 gValLgValR 函数)。

    以求左侧部分的前缀哈希值为例,如果要求长度为 (0) 或总长度就可以直接返回已知结果。
    否则考虑要求的长度是否全部落在左子树的左侧部分内,如果是则直接递归左子树。
    否则就是有一部分在左子树的左侧部分内,另一部分在右子树的左侧部分内,但是合并的时候会被左子树的右侧部分抵消掉一个前缀的数值,所以递归查询的时候,长度要减去左子树的左侧部分长度,但又要加上左子树的右侧部分长度,求出后再抵消掉左子树的右侧部分的一个前缀,再在前面拼上左子树的左侧部分。

    右侧部分的后缀哈希值是对称的。虽然描述起来有些困难,但是如果封装了字符串哈希的一些操作,写起来会十分的简便。

    可以发现若要查询此信息,每次只会递归到一个子树内,所以每次合并信息的复杂度是 (mathcal O (log n)) 的。

    每次修改总共要合并 (mathcal O (log n)) 次信息,总复杂度为 (mathcal O (log^2 n))这与李超线段树或类楼房重建线段树的结构有些相似

    这部分就是修改时线段树内的维护了。还需要考虑询问时。

    可以发现询问时,信息合并之后不再是一个原线段树上的节点了,而可以看作是「询问时建立的虚节点」。

    因为信息合并时同样需要进行子树递归,在传统的递归结构中这是比较难以维护的。

    我实现时先把在线段树上定位到的区间抽离出来,从左到右塞进一个栈中(详见代码中的 Extract 函数)。

    然后利用询问时的特性(在任意时刻前缀不能含有左侧的右括号),维护一个 seq 数组进行更简易的模拟。

    注意类似 gValR 的函数还需要针对 seq 实现一个,就是代码中的 gVal 函数。

    具体细节详见代码。

    评测记录,下面是代码,时间复杂度为 (mathcal O (n log^2 n)),空间复杂度为 (mathcal O (n))

    #include <cstdio>
    
    const int MN = 100005, MS = 1 << 18 | 7;
    
    namespace Hashing {
    	typedef long long LL;
    	const int Mod = 998244353, B = 114514, iB = 137043501;
    	int _w[MN * 2], *w = _w + MN;
    	void Init(int N) {
    		w[0] = 1;
    		for (int i = 1; i <= N; ++i)
    			w[i] = (LL)w[i - 1] * B % Mod,
    			w[-i] = (LL)w[-i + 1] * iB % Mod;
    	}
    	struct str {
    		int x, m;
    		str() { x = m = 0; }
    		str(int v) { x = v, m = 1; }
    		str(int _x, int _m) : x(_x), m(_m) {}
    		friend bool operator == (str a, str b) { return a.m == b.m && a.x == b.x; }
    		friend str operator + (str a, str b) { return str((a.x + (LL)b.x * w[a.m]) % Mod, a.m + b.m); }
    		friend str operator - (str a, str b) { return str((LL)(a.x - b.x + Mod) * w[-b.m] % Mod, a.m - b.m); }
    	};
    }
    using Hashing::str;
    
    int N, Q;
    
    #define li (i << 1)
    #define ri (li | 1)
    #define mid ((l + r) >> 1)
    #define ls li, l, mid
    #define rs ri, mid + 1, r
    struct dat {
    	bool err;
    	str vl, vr;
    	dat() { err = 0; }
    	dat(int x) { err = 0; x > 0 ? vr = str(x) : vl = str(-x); }
    } tr[MS];
    str gValL(int i, int k) {
    	if (!k) return str();
    	if (k == tr[i].vl.m) return tr[i].vl;
    	if (k <= tr[li].vl.m) return gValL(li, k);
    	return tr[li].vl + (gValL(ri, k - tr[li].vl.m + tr[li].vr.m) - tr[li].vr);
    }
    str gValR(int i, int k) {
    	if (!k) return str();
    	if (k == tr[i].vr.m) return tr[i].vr;
    	if (k <= tr[ri].vr.m) return gValR(ri, k);
    	return tr[ri].vr + (gValR(li, k - tr[ri].vr.m + tr[ri].vl.m) - tr[ri].vl);
    }
    void Upd(int i) {
    	if (tr[li].err || tr[ri].err) return tr[i].err = 1, void();
    	tr[i].err = 0;
    	tr[i].vl = tr[li].vl;
    	tr[i].vr = tr[ri].vr;
    	if (tr[li].vr.m <= tr[ri].vl.m) {
    		if (tr[li].vr == gValL(ri, tr[li].vr.m))
    			tr[i].vl = tr[i].vl + (tr[ri].vl - tr[li].vr);
    		else tr[i].err = 1;
    	} else {
    		if (tr[ri].vl == gValR(li, tr[ri].vl.m))
    			tr[i].vr = tr[i].vr + (tr[li].vr - tr[ri].vl);
    		else tr[i].err = 1;
    	}
    }
    void Build(int i, int l, int r) {
    	if (l == r) {
    		int x;
    		scanf("%d", &x);
    		tr[i] = x;
    		return ;
    	}
    	Build(ls), Build(rs);
    	Upd(i);
    }
    void Mdf(int i, int l, int r, int p, int x) {
    	if (l == r) return tr[i] = x, void();
    	p <= mid ? Mdf(ls, p, x) : Mdf(rs, p, x);
    	Upd(i);
    }
    
    int stk[45], tp;
    void Extract(int i, int l, int r, int a, int b) {
    	if (r < a || b < l) return ;
    	if (a <= l && r <= b) return stk[++tp] = i, void();
    	Extract(ls, a, b), Extract(rs, a, b);
    }
    str seq[45];
    str gVal(int i, int k) {
    	if (!k) return str();
    	if (k == seq[i].m) return seq[i];
    	if (k <= tr[stk[i]].vr.m) return gValR(stk[i], k);
    	return tr[stk[i]].vr + (gVal(i - 1, k - tr[stk[i]].vr.m + tr[stk[i]].vl.m) - tr[stk[i]].vl);
    }
    bool Qur(int l, int r) {
    	tp = 0, Extract(1, 1, N, l, r);
    	for (int i = 1; i <= tp; ++i) {
    		if (tr[stk[i]].err) return 0;
    		if (seq[i - 1].m < tr[stk[i]].vl.m) return 0;
    		if (tr[stk[i]].vl == gVal(i - 1, tr[stk[i]].vl.m))
    			seq[i] = tr[stk[i]].vr + (seq[i - 1] - tr[stk[i]].vl);
    		else return 0;
    	}
    	return !seq[tp].m;
    }
    
    int main() {
    	scanf("%d%*d", &N);
    	Hashing::Init(N);
    	Build(1, 1, N);
    	scanf("%d", &Q);
    	while (Q--) {
    		int t, x, y;
    		scanf("%d%d%d", &t, &x, &y);
    		if (t == 1) Mdf(1, 1, N, x, y);
    		else puts(Qur(x, y) ? "Yes" : "No");
    	}
    	return 0;
    }
    
  • 相关阅读:
    【杭电】[4883]TIANKENG’s restaurant
    【杭电】[4883]TIANKENG’s restaurant
    【POJ】[1423]Big Number
    【POJ】[1423]Big Number
    【杭电】[1280]前m大的数
    【杭电】[1280]前m大的数
    【杭电】[1576]A/B
    【杭电】[1576]A/B
    [leetcode]346. Moving Average from Data Stream滑动窗口平均值
    [leetcode]99. Recover Binary Search Tree恢复二叉搜索树
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/CF1340F.html
Copyright © 2011-2022 走看看