C. Brackets Stack Query

合法括号序列需满足以下两个条件:

  • 左右括号数相等
  • 任意前缀中左括号数至少是右括号数

一般,我们遇到左括号,记为 +1,遇到右括号,记为 -1,然后维护前缀和。
那么,第一个条件就是 \(S_n = 0\),第二个条件就是 \(\min(S_i) = 0\)

在本题中,我们可以用一个栈来维护前缀和,遇到负数就将它变成 \(-\infty\),这样对于每次询问只需判定栈顶的元素是否为 \(0\) 即可

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

const int INF = 1001001001;

int main() {
    int q;
    cin >> q;
    
    vector<int> x(1);
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            char c;
            cin >> c;
            int nx = x.back() + (c=='(' ? 1 : -1);
            if (nx < 0) nx = -INF;
            x.push_back(nx);
        }
        else {
            x.pop_back();
        }
        if (x.back() == 0) puts("Yes");
        else puts("No");
    }
    
    return 0;
}

D. 183184

\(y=C+x\)(题目里我们要枚举 \(x\in[1,D]\),等价于枚举 \(y\in(C,C+D]\))。
\(y\) 的十进制位数记为 \(d\)
把拼接写成数值形式:

\[f(C,y)=C\cdot 10^d + y \]

因此,固定 \(d\)(即固定 \(y\) 的位数),当 \(y\) 遍历区间 \([10^{d-1},10^d-1]\) 时,\(f(C,y)\) 的取值正好是区间

\[\big[C\cdot 10^d + 10^{d-1}, C\cdot 10^d + (10^d-1)\big] \]

于是问题变为:在这些区间中,有多少个整数是完全平方数?把所有位数的区间与 \((C, C+D]\)\(y\)-范围相交后计数即可。
注意:取平方根可以用 long double 类型的 sqrtl 函数或写二分

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

// ll s(ll r) {
//     ll ac = 0, wa = 2e9;
//     while (ac+1 < wa) {
//         ll wj = (ac+wa)/2;
//         if (wj*wj <= r) ac = wj; else wa = wj;
//     }
//     return ac;
// }
ll s(ll r) {
    return sqrtl(r);
}
ll s(ll l, ll r) {
    return s(r) - s(l-1);
}
ll g(ll c, ll r) {
    ll res = 0;
    for (ll l = 1;; l *= 10) {
        ll base = c*l*10;
        ll nl = base+l, nr = base+(l*10-1);
        nr = min(nr, base+r);
        if (nr < nl) return res;
        res += s(nl, nr);
    }
}

void solve() {
    ll c, d;
    cin >> c >> d;
    
    ll ans = g(c, c+d) - g(c, c);
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

E. Farthest Vertex

经典结论:

距离每个点最远的点一定是直径两端点中的其中之一。

对于取编号最大的点,可以简单的用 std::pair<int, int> 维护 (深度,点的编号) 进行比较即可

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

int main() {
    int n;
    cin >> n;
    
    vector<vector<int>> to(n);
    rep(i, n-1) {
        int a, b;
        cin >> a >> b;
        --a; --b;
        to[a].push_back(b);
        to[b].push_back(a);
    }
    
    vector<P> ans(n);
    auto dfs = [&](auto& f, int sv, int v, int p=-1, int d=0) -> P {
        ans[v] = max(ans[v], P(d, sv));
        P res(d, v);
        for (int u : to[v]) {
            if (u == p) continue;
            res = max(res, f(f, sv, u, v, d+1));
        }
        return res;
    };
    
    int a = dfs(dfs, 0, 0).second;
    int b = dfs(dfs, a, a).second;
    dfs(dfs, b, b);
    
    rep(v, n) cout << ans[v].second+1 << '\n';
    
    return 0;
}

F. Pyramid Alignment

每个区間 \(i\) 的长度是固定的 \(W_i\)(且 \(W_1<W_2<\dots\))。任意时刻区間 \(i\) 可以由一个“锚点”和方向决定:

  • 如果我们知道区間 \(i\)左端坐标为 \(a\),那么区間就是 \([a,\;a+W_i]\)
  • 如果我们知道区間 \(i\)右端坐标为 \(b\),那么区間就是 \([b-W_i,\;b]\)

维护按索引单调划分的块——更新时从栈顶弹出失效块再压入新块以完成前缀对齐,查询则在栈上二分定位第一个包含目标点的索引并据此计算答案。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

struct B {
    int l, x; char s;
};

int main() {
    int n;
    cin >> n;
    
    vector<int> w(n);
    rep(i, n) cin >> w[i];
    
    int q;
    cin >> q;
    
    vector<B> bs;
    bs.emplace_back(n, 0, 'L');
    bs.emplace_back(0, 0, 'L');
    auto get = [&](B b, int i) -> P {
        if (b.s == 'L') return P(b.x, b.x+w[i]);
        return P(b.x-w[i], b.x);
    };
    
    rep(qi, q) {
        int type, x;
        cin >> type >> x;
        if (type == 3) {
            int ac = 0, wa = bs.size();
            while (ac+1 < wa) {
                int wj = (ac+wa)/2;
                auto [l, r] = get(bs[wj], bs[wj].l);
                if (l <= x and x < r) ac = wj; else wa = wj;
            }
            int ans = n;
            if (wa < bs.size()) {
                int bi = wa;
                ac = bs[bi].l; wa = bs[bi-1].l; // ac: not contain, wa: contain
                while (ac+1 < wa) {
                    int wj = (ac+wa)/2;
                    auto [l, r] = get(bs[bi], wj);
                    if (l <= x and x < r) wa = wj; else ac = wj;
                }
                ans = n-wa;
            }
            cout << ans << '\n';
        }
        else {
            x--;
            if (x == 0) continue;
            while (1) {
                B b = bs.back(); bs.pop_back();
                if (x < bs.back().l) {
                    bs.emplace_back(x, b.x, b.s);
                    break;
                }
            }
            {
                auto [l, r] = get(bs.back(), bs.back().l);
                if (type == 1) bs.emplace_back(0, l, 'L');
                else bs.emplace_back(0, r, 'R');
            }
        }
    }
    
    return 0;
}

G. Necklace

\(\mathrm{Burnside}\) 引理。
令序列长度为 \(N\),则群作用对应 \(N\) 种移位量。对于移位量 \(c\),不动的条件是:令 \(k:=\gcd(N,c), \; s:= \frac{N}{k}\),当把序列分成 \(k\) 个大小为
\(s\) 的组时,每个组内的元素都必须相同。对于每个 \(s\),通过 \(\mathrm{dp}\) 处理它对 \(f(2),…,f(U)\) 的贡献。
dp[i][j] 表示当前总乘积为 \(i\) 且已选 \(j\) 个元素时的方案数。