AtCoder Beginner Contest 450 题解(A-F)

A - 3,2,1,GO

签到

void zyb() {
    int n;
    cin >> n;
    for(int i = n; i >= 1; i --) {
        if(i == n) cout << i;
        else cout << "," << i;
    }
}

B - Split Ticketing

数据量够三层 for 循环

void zyb() {
    int n;
    cin >> n;
    vector<vector<int>> C(n+1, vector<int>(n+1));
    for(int i = 1; i <= n; i ++) {
        for(int j = i+1; j <= n; j ++) {
            cin >> C[i][j];
        }
    }
    for(int i = 1; i <= n; i ++) {
        for(int j = i+1; j <= n; j ++) {
            for(int k = j+1; k <= n; k ++) {
                if(C[i][k] > C[i][j]+C[j][k]) {
                    cout << "Yes\n";
                    return;
                }
            }
        }
    }
    cout << "No\n";
}

C - Puddles

只需要先对所有边界位置的空地进行一次 bfs,将连通块变为 #,那么剩余的 . 联通块个数,就全部是不含边界位置的。

void zyb() {
    int h, w;
    cin >> h >> w;
    vector<vector<char>> S(h+1, vector<char>(w+1));
    vector<vector<int>> vis(h+1, vector<int>(w+1));
    for(int i = 1; i <= h; i++) {
        string s;  cin >> s;
        for(int j = 1; j <= w; j ++) {
            S[i][j] = s[j-1];
        }
    }
    auto bfs = [&](int i, int j)->void {
        queue<pii> q;
        q.push({i, j});
        while(q.size() > 0) {
            auto u = q.front();
            q.pop();
            int x = u.first, y = u.second;
            if(vis[x][y]) continue;
            S[x][y] = '#';
            vis[x][y] = true;
            for(int k = 0; k < 4; k ++) {
                int xx = dx[k]+x, yy = dy[k]+y;
                if(xx < 1 || xx > h || yy < 1 || yy > w) continue;
                if(vis[xx][yy] || S[xx][yy] == '#') continue;
                q.push({xx, yy});
            }
        }
    };
    for(int i = 1; i <= h; i ++) {
        for(int j = 1; j <= w; j ++) {
            if((i == 1 || i == h || j == 1 || j == w) && S[i][j] == '.') {
                bfs(i, j);
            }
        }
    }
    int cnt = 0;
    for(int i = 1; i <= h; i ++) {
        for(int j = 1; j <= w; j ++) {
            if(S[i][j] == '.') {
                bfs(i, j);
                cnt ++;
            }
        }
    }
    cout << cnt << "\n";
}

D - Minimize Range

要对所有数字加不定量个 k,由于:(a[i]+m*k)%k = a[i]%k,考虑对所有数取模。将 max-min 转化为:将这些数字映射到长度不超过 k 的环形区间上,求能覆盖所有数字的最小长度。
想象一个圆,所有数按大小顺时针在圆上。覆盖所有数,有两种方法:

  • 从最小数顺时针一直覆盖到最大数
  • 从任意某一个数开始,一直顺时针,穿过起点,一直覆盖到比它的前一个数为止

两种方法取最小值即可。

void zyb() {
    /* 
    由给每个数反复多次 +k 想到 (a[i]+m*k)%k = a[i]%k
    然后想到 a[i]%k 会永远不变,并且是小于 k 的
    现在将最小化 max(a)-min(a) 这个问题
    转化为让所有数落在一个尽可能短的"环形"区间内
    这个最短的长度可能是 a[n]-a[1](可以包含全部数)
    也可能是任意一个 ai 在环形圆里转个圈,超过一圈后返回 a(i-1)
    这样也能覆盖所有的数,长度为 a[i]+k-a[i+1]
    */
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 1);
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        a[i] %= k;
    }
    sort(a.begin()+1, a.end());
    int mi = a[n]-a[1];
    for(int i = 1; i < n; i ++) {
        mi = min(mi, a[i]+k-a[i+1]);
    }
    cout << mi << "\n";
}

E - Fibonacci String

根据这个类斐波那契数列的规则,字符串的个数在有限范围内不会超过90,因此,可以先将前90个规模的字符串的信息给处理出来。
问题是要求 [l,r] 里的 ch 字符数量,那么是可以转化为先求 [1,r]ch字符的,再求 [1,l-1]里的ch字符数量,然后相减。所以问题就转化为了在一个极长的目标字符串里,求 [1,k] 的某个字符的数量。
题目中的规律告诉我们,第i个字符串 Si,一定是第i+1个字符串 S(i+1)的前缀。
所以假设我们所要求的那个 k,长度上最大是小于第 x 个字符串的长度的话,那么它一定是大于第 x-1 个字符串的,并且因为第 x-1 个字符串是第 x 个字符串的前缀,所以第x-1个字符串里的东西,全部是我们所需要统计的,那么,我们可以先统计第 x-1 个,然后剩余的那些,肯定也是第 x-2 个字符串的前缀,同时,第 x-2 个字符串又由第 x-3 和第 x-4 个字符串组成…如何形成一个递归的思路
我们在使用 sz[i] 来记录第 i 个字符串的个数,每次当我们需要找当前需要的是第几个字符串的时候,只需要使用二分查找,找到大于当前长度的第一个字符串即可。下标减1后就可以得到我们可以完全统计的那个字符串。然后将 k 减去这个长度,继续循环找。

void zyb() {
    string x, y;
    cin >> x >> y;
    // sz[i]:第i个中字符的个数
    // numx[i]:第i个中包含字符串x的个数
    // numy[i]:第i个中包含字符串y的个数
    vector<i64> sz, numx, numy;
    sz.push_back(x.size()), sz.push_back(y.size());
    numx.push_back(1), numx.push_back(0);
    numy.push_back(0), numy.push_back(1);
    while(1) {
        int len = sz.size(), lennx = numx.size(), lenny = numy.size();
        sz.push_back(sz[len-1] + sz[len-2]);
        numx.push_back(numx[lennx-1]+numx[lennx-2]);
        numy.push_back(numy[lenny-1]+numy[lenny-2]);
        if(sz.back() > lnf) break;
    }
    int n = x.size(), m = y.size();

    // 对于单个字符串,每个字符出现的前缀和
    vector<vector<int>> cntx(n+1, vector<int>(26, 0));
    vector<vector<int>> cnty(m+1, vector<int>(26, 0));

    for(int i = 1; i <= n; i ++) {
        for(int j = 0; j < 26; j ++) {
            cntx[i][j] = cntx[i-1][j];
            if(x[i-1]-'a' == j) cntx[i][j] ++;
        }
    }
    for(int i = 1; i <= m; i ++) {
        for(int j = 0; j < 26; j ++) {
            cnty[i][j] = cnty[i-1][j];
            if(y[i-1]-'a' == j) cnty[i][j] ++;
        }
    }
    // 假设 x = abc
    // y = defgh
    // 那么最终生成的字符串一定是 defghabc... y 字符串在前面
    /* 由于 s[i] = s[i-1]+s[i-2]
    那么从 s[i] 中减去一个前缀 s[i-k], 那么 s[i-k]一定是 s[i-k+1]的前缀
    s[i-k+1] 一定是 s[i-k+2] 的前缀, 所以 s[i-k] 一定是 s[i] 的前缀
    并且 s[i-k] 和 s[i-k-1] 也一定是连在一起的, s[i-k] 在前
    由于找到的是最大的 idx, 所以接下来计算的长度, 一定是小于sz[i-k]的
    那么剩余的没有计算的部分, 就一定是 s[i-k-1] 的前缀
    */
    auto cal = [&](i64 c, char ch) -> i64 {
        i64 sum = 0;
        while(c > 0) {
            if(c <= m) {
                sum += cnty[c][ch-'a'];
                break;
            }
            if(c < m+n) {
                sum += cnty[m][ch-'a'] + cntx[c-m][ch-'a'];
                break;
            }
            // c > n+m 说明一定是第三个字符串及其之后的字符串作为这部分的前缀
            int idx = (upper_bound(sz.begin(), sz.end(), c)-sz.begin())-1;
            sum += numx[idx]*cntx[n][ch-'a'] + numy[idx]*cnty[m][ch-'a'];
            c -= sz[idx];
        }
        return sum;
    };

    int q;
    cin >> q;
    while(q --) {
        i64 l, r;
        char ch;
        cin >> l >> r >> ch;
        i64 ans = cal(r, ch) - cal(l-1, ch);
        cout << ans << "\n";
    }
}

F - Strongly Connected 2

图强连通(strongly connected)的条件:图中任意两个点uv,都存在从uv和从vu的路径。
题目已经规定了,从所有的大于 1 的点 i 都一定有一条路可以到 i-1 点,那么我们只需要保证从 1 可以到 2,从 2 可以到 3,3 可以到 4,n-1 可以到 n 即可…
定义 dp[i] 为从 1 号点恰好能到 i 号点的方案数。
从小到大枚举每个点,假设 i 号点有一条通向 e 的路径,那么 dp[e] 应该加上 [i, e-1] 这个区间所有的贡献,因为 i+1 可以到 i,那么这些路径都可以先走到 i+k 这个点,再走到 i,然后走到 e,同时,因为这条边是由 i 走向 e 的,所以所有大于等于 e 的所有最远点都和这条边没有关系,但这条边有没有是两种状态,因此 dp[e], dp[e+1]......,dp[n]× 2
同时细节是对于每个点的所有边,需要从大到小枚举可达点,这样,方案数乘 2 操作就不会影响到较小 e 的 sum(dp[i+1],dp[i+2],...dp[e-1]),更新就不会出错。

struct SegmentTree {
    int n;
    vector<i64> a;
    vector<i64> sum, add, mul;

    SegmentTree(vector<i64>& tmp) {
        a = tmp;
        n = tmp.size() - 1;
        sum.resize(4 * n, 0), add.resize(4 * n, 0), mul.resize(4 * n, 1);
        init(1, n, 1);
    }

    void up(int i) {
        sum[i] = (sum[i * 2] + sum[i * 2 + 1]) % mod;
    }

    // (x + add) * mul x*mul + add*mul
    void init(int l, int r, int i) {
        if (l == r) {
            sum[i] = a[l] % mod;
            return;
        }
        int mid = (l + r) / 2;
        init(l, mid, i * 2);
        init(mid + 1, r, i * 2 + 1);
        up(i);
    }

    void lazy_add(int i, i64 k, int l, int r) {
        add[i] += k; add[i] %= mod;
        sum[i] += (r - l + 1) * k; sum[i] %= mod;
    }
    void lazy_mul(int i, i64 k, int l, int r) {
        mul[i] *= k; mul[i] %= mod;
        sum[i] *= k; sum[i] %= mod;
        add[i] *= k; add[i] %= mod;
    }

    void down(int i, int l, int r) {
        int mid = (l + r) / 2;
        if (mul[i] != 1) {
            sum[i * 2] *= mul[i]; sum[i * 2] %= mod;
            sum[i * 2 + 1] *= mul[i]; sum[i * 2 + 1] %= mod;
            add[i * 2] *= mul[i]; add[i * 2] %= mod;
            add[i * 2 + 1] *= mul[i]; add[i * 2 + 1] %= mod;
            mul[i * 2] *= mul[i]; mul[i * 2] %= mod;
            mul[i * 2 + 1] *= mul[i]; mul[i * 2 + 1] %= mod;
            mul[i] = 1;
        }
        if (add[i]) {
            lazy_add(i * 2, add[i], l, mid);
            lazy_add(i * 2 + 1, add[i], mid + 1, r);
            add[i] = 0;
        }
    }

    void modify_mul(int l, int r, int i, int jl, int jr, i64 k) {
        if (jl <= l && jr >= r) {
            lazy_mul(i, k, l, r);
            return;
        }
        down(i, l, r);
        int mid = (l + r) / 2;
        if (jl <= mid) modify_mul(l, mid, i * 2, jl, jr, k);
        if (jr > mid) modify_mul(mid + 1, r, i * 2 + 1, jl, jr, k);
        up(i);
    }

    void modify_add(int l, int r, int i, int jl, int jr, i64 k) {
        if (jl <= l && jr >= r) {
            lazy_add(i, k, l, r);
            return;
        }
        down(i, l, r);
        int mid = (l + r) / 2;
        if (jl <= mid) modify_add(l, mid, i * 2, jl, jr, k);
        if (jr > mid) modify_add(mid + 1, r, i * 2 + 1, jl, jr, k);
        up(i);
    }

    i64 query(int l, int r, int i, int jl, int jr) {
        if (jl <= l && jr >= r) {
            return sum[i];
        }
        int mid = (l + r) / 2;
        down(i, l, r);
        i64 res = 0;
        if (jl <= mid) res += query(l, mid, i * 2, jl, jr); res %= mod;
        if (jr > mid) res += query(mid + 1, r, i * 2 + 1, jl, jr); res %= mod;
        return res;
    }

    void modify_mul(int jobl, int jobr, i64 k) {
        modify_mul(1, n, 1, jobl, jobr, k);
    }
    void modify_add(int jobl, int jobr, i64 k) {
        modify_add(1, n, 1, jobl, jobr, k);
    }
    i64 query(int jobl, int jobr) {
        return query(1, n, 1, jobl, jobr);
    }
};

void zyb()
{
    int n, m;
    cin >> n >> m;
    vector<vector<int>> edg(n + 1);
    for(int i = 0; i < m; i ++) {
        int u, v;
        cin >> u >> v;
        edg[u].push_back(v);
    }
    // dp[i]:1到达i,有多少种方法
    vector<i64> dp(n + 1);
    SegmentTree seg(dp);
    seg.modify_add(1, 1, 1);
    for(int i = 1; i <= n; i ++) {
        sort(edg[i].begin(), edg[i].end(), greater<int>());
    }
    for(int i = 1; i <= n; i ++) {
        for(int e : edg[i]) {
            // dp[e] += (dp[1]...dp[e-1]);
            i64 sum = seg.query(i, e-1);
            seg.modify_mul(e, n, 2ll); // e->n 的全部乘 2
            seg.modify_add(e, e, sum);
        }
    }

    i64 ans = seg.query(n, n);
    cout << ans << "\n";
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    // cin >> T;
    while(T --) {
        zyb();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值