注意这是个合法括号序列,所以我们可以看成是两颗有根树,且孩子是有先后关系的。删去一个 ()
形状就等价于删去树的一个叶节点。另一个角度就是我们能否将 T2 “嵌入”到树 T1 中去。我们注意到这是一个子序列问题,所以对于 T2 的每个子树,肯定是在 T1 的子树中越靠前放进去越好。因此我们可以简单地写出一个 check 函数:
bool check(int u1, int u2) { int j = 0; for (int v : T2[u2]) { while (j < T1[u1].size() && !check(T1[u1][j], v)) ++j; if (j == T1[u1].size()) return false; ++j; } return true; }
这个函数虽然是个 DFS,但是它其实是线性的,道理很简单,在寻找匹配的过程中,T1 的每个节点只会最多一次尝试与 T2 中的一个节点配对。因此总时间不超过 。综上,复杂度 。
#include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <ctime> #include <cctype> #include <algorithm> #include <stack> #include <random> #include <bitset> #include <queue> #include <functional> #include <set> #include <map> #include <vector> #include <chrono> #include <iostream> #include <limits> #include <numeric> #define LOG(FMT...) fprintf(stderr, FMT) using namespace std; typedef long long ll; typedef unsigned long long ull; // mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); template <class T> istream& operator>>(istream& is, vector<T>& v) { for (T& x : v) is >> x; return is; } template <class T> ostream& operator<<(ostream& os, const vector<T>& v) { if (!v.empty()) { os << v.front(); for (int i = 1; i < v.size(); ++i) os << ' ' << v[i]; } return os; } const int N = 110; int m1, m2; vector<int> T1[N], T2[N]; void build(const string& s, int& m, vector<int> T[]) { stack<int> stk; stk.push(m = 1); for (char c : s) if (c == '(') { T[stk.top()].push_back(++m); stk.push(m); } else stk.pop(); } bool check(int u1, int u2) { int j = 0; for (int v : T2[u2]) { while (j < T1[u1].size() && !check(T1[u1][j], v)) ++j; if (j == T1[u1].size()) return false; ++j; } return true; } int main() { #ifdef LBT freopen("test.in", "r", stdin); int nol_cl = clock(); #endif ios::sync_with_stdio(false); cin.tie(nullptr); string s; cin >> s; build(s, m1, T1); cin >> s; build(s, m2, T2); cout << (check(1, 1) ? "Possible" : "Impossible"); #ifdef LBT LOG("Time: %dms\n", int ((clock() -nol_cl) / (double)CLOCKS_PER_SEC * 1000)); #endif return 0; }