2024 停课做题总结

bryce蒟蒻的小窝 / 2024-10-13 / 原文

[ABC372D] Buildings

思路

正着做不方便,倒着用单调栈做一遍就行了。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int n, ans[N];
int h[N], stk[N], top;

int main(){
    n = read();
    for (int i = 1; i <= n; i++) h[i] = read();
    h[0] = 0x7fffffff;
    for (int i = n; i >= 1; i--){
        ans[i] = top;
        while (h[stk[top]] < h[i]) top--;
        stk[++top] = i;
    }
    for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
    return 0;
}

[ABC372E] K-th Largest Connected Components

思路

注意到,\(k\le 10\),所以暴力维护前 \(10\) 个点,然后使用并查集实现连边,利用归并排序实现前 \(10\) 个点,注意:自己也算能走到自己。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 15;
int n, q;
int fa[N], e[N][M], a[N], len[N];
int find(int x){
    return (x == fa[x] ? fa[x] : fa[x] = find(fa[x]));
}
void merge(int x, int y){
    int fx = find(x), fy = find(y);
    if (fx == fy) return;
    fa[fx] = fy;
    for (int i = 1, j = 1, k = 1; k <= min(10, len[fx] + len[fy]); k++){
        if (e[fx][i] > e[fy][j]) a[k] = e[fx][i++];
        else a[k] = e[fy][j++];
    }
    len[fy] = min(10, len[fx] + len[fy]);
    for (int i = 1; i <= len[fy]; i++) e[fy][i] = a[i];
}

int main(){
    n = read(), q = read();
    for (int i = 1; i <= n; i++) fa[i] = i, e[i][1] = i, len[i] = 1;
    for (int i = 1; i <= q; i++){
        int opt = read(), u = read(), v = read();
        if (opt == 1) merge(u, v);
        else{
            int x = find(u);
            cout << (e[x][v] == 0 ? -1 : e[x][v]) << '\n';
        }
    }
    return 0;
}

[ABC372F] Teleporting Takahashi 2

思路

好题!看完题目,可以很快想到一个 \(dp\),但是空间和时间复杂度显然都不允许,于是集中注意力,发现 \(m\le 50\),这意味着只有不到 \(100\) 个点转移时会变,那么,就把两两之间有多余连边的点中间没有多余连接的点看成一条边,边权为所有边的总和,接着 \(dp\)

考虑怎么统计答案,很显然,有多余连接的点可以直接求,而没有多余连接的点就是它所在的有向边指向的点的 \(k - i\) 步的方案数(\(i\) 为自己到达有向边指向的点的距离)。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 55, K = 2e5 + 10, mod = 998244353;
int n, m, k, ans;
int x[M], y[M];
bool vis[N];
struct edge{
    int v, w, nxt;
}e[N];
int head[N], cnt, id[N], tot;
void add(int u, int v, int w){
    e[++cnt] = (edge){v, w, head[u]};
    head[u] = cnt;
}
int dp[M << 2][K << 2];

signed main(){
    n = read(), m = read(), k = read();
    for (int i = 1; i <= m; i++) x[i] = read(), y[i] = read(), add((id[x[i]] == 0 ? id[x[i]] = ++tot : id[x[i]]), (id[y[i]] == 0 ? id[y[i]] = ++tot : id[y[i]]), 1);
    for (int i = 1, last = 1; i <= n; i++){
        bool ok = 0;
        vis[i] = 1;
        for (int j = 1; j <= m; j++) if (x[j] == i || y[j] == i) ok = 1;
        if (ok && i != 1) add((id[last] == 0 ? id[last] = ++tot : id[last]), (id[i] == 0 ? id[i] = ++tot : id[i]), i - last), vis[i] = 0, last = i;
        if (i == n){
            if (last != 1) add((id[last] == 0 ? id[last] = ++tot : id[last]), (id[1] == 0 ? id[1] = ++tot : id[1]), n - last + 1);
            else add((id[last] == 0 ? id[last] = ++tot : id[last]), 1, n);
        }
    }
    vis[1] = 0;
    dp[(id[1] == 0 ? id[1] = ++tot : id[1])][0] = 1;
    for (int i = 0; i <= k; i++){
        for (int u = 1; u <= tot; u++){
            for (int j = head[u]; j; j = e[j].nxt){
                int v = e[j].v, w = e[j].w;
                if (i + w <= k) dp[v][i + w] = (dp[v][i + w] + dp[u][i]) % mod;
            }
        }
    }
    for (int i = 1, last = 1; i <= n; i++){
        if (!vis[i]) ans = (ans + dp[(id[i] == 0 ? id[i] = ++tot : id[i])][k]) % mod, last = i;
        else{
            ans = (ans + dp[(id[last] == 0 ? id[last] = ++tot : id[last])][k - (i - last)]) % mod;
        }
    }
    cout << ans;
    return 0;
}

Invertible Bracket Sequences

思路

考虑一个合法的括号序列是怎样的,将 "(" 抽象成 \(1\),将 ")" 抽象成 \(-1\),那么整个序列的和为 \(0\),同时每个位置的前缀和 \(\ge 0\)

枚举一个反转的左端点,看有多少个右端点能与它反转后符合题目条件。设左端点为 \(l\),右端点为 \(r\),前缀和为 \(pre\),反转区间的和为 \(pre'\),那么 \(pre_r = pre_{l - 1} + pre'\) ,反转过后,\(pre_r\ge 0,pre'\rightarrow -pre'\),所以 \(pre_{l - 1}\ge pre'\),代入上式,\(2pre_{l - 1}\ge pre_r\),且 \(l\)\(r\) 之间的所有 \(pre_i\) 都要满足上式,而一个反转区间的和为 \(0\),即 \(pre'=pre_{l - 1}\),所以倒着做,存下等于 \(pre_{l - 1}\)\(pre'\) 的位置,对于一个 \(l\),求 \(l\)\(pre'\) 的位置之间的最大值,如果最大值满足 \(pre_{l - 1}\ge maxn\) 那么这个位置一定可以,于是二分求最多的 \(r\)

代码

#include<iostream>
#include<cstring>
#include<vector>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int T, n;
long long ans;
char s[N];
int a[N], pre[N], t[N << 2];
void build(int now, int l, int r){
    if (l == r){
        t[now] = pre[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(now << 1, l, mid);
    build(now << 1 | 1, mid + 1, r);
    t[now] = max(t[now << 1], t[now << 1 | 1]);
}
int query(int now, int l, int r, int x, int y){
    if (x <= l && r <= y) return t[now];
    int mid = (l + r) >> 1, res = 0;
    if (x <= mid) res = max(res, query(now << 1, l, mid, x, y));
    if (mid + 1 <= y) res = max(res, query(now << 1 | 1, mid + 1, r, x, y));
    return res;
}
vector<int> cnt[N];

signed main(){
    T = read();
    while (T--){
        cin >> s + 1;
        n = strlen(s + 1);
        for (int i = 1; i <= n; i++) a[i] = (s[i] == '(' ? 1 : -1), pre[i] = pre[i - 1] + a[i];
        build(1, 1, n);
        for (int i = n; i >= 1; i--){
            int l = 0, r = cnt[pre[i]].size() - 1, k = r + 1;
            while (l <= r){
                int mid = (l + r) >> 1, res = query(1, 1, n, i, cnt[pre[i]][mid]);
                if (2 * pre[i] >= res){
                    r = mid - 1;
                    k = mid;
                }else{
                    l = mid + 1;
                }
            }
            ans += cnt[pre[i]].size() - k;
            cnt[pre[i]].emplace_back(i);
        }
        cout << ans << '\n';
        for (int i = 0; i <= n; i++) cnt[i].clear();
        ans = 0;
    }
    return 0;
}