Codeforces Round 966 (Div. 3)

Codeforces Round 966 (Div. 3)

A. Primary Task

思路

\(false\) 的情况:1、\(s.size()\le 2\) ;2、\(s\) 不以10开头;3、\(s\) 2位以后得字符串转整型后小于 \(2\) 或者含有前导零。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    string s;
    cin >> s;

    if (s.size() <= 2) {
        cout << "NO\n";
        return ;
    }

    cout << (s.substr(0, 2) == "10" && stoi(s.substr(2)) >= 2 && to_string(stoi(s.substr(2))) == s.substr(2) ? "YES" : "NO") << '\n';

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

B. Seating in a Bus

思路

\(set\) 维护当前位是不是等于已插入数 最大+1 或者最小-1

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    int n;
    cin >> n;

    vector<int> a(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
    }

    set<int> s;
    s.insert(a[1]);
    for (int i = 2; i <= n; i ++) {
        if (a[i] == *s.rbegin() + 1 || a[i] == *s.begin() - 1) {
            s.insert(a[i]);
        } else {
            cout << "NO\n";
            return ;
        }
    }

    cout << "YES\n";

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

C. Numeric String Template

思路

预处理数字串中相同数字的所有位置,然后对字符串也进行相应的处理,最后判断字符串的位置是否合法即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    int n;
    cin >> n;

    map<int, int> mp;
    vector<int> a(n);
    vector ve(n, vector<int>());
    for (int i = 0; i < n ; i ++) {
        cin >> a[i];
        if (!mp.count(a[i])) {
            mp[a[i]] = i;
            ve[i].push_back(i);
        } else {
            ve[mp[a[i]]].push_back(i);
        }
    }

    int m;
    cin >> m;
    while (m--) {
        string s;
        cin >> s;

        if (s.size() != n) {
            cout << "NO\n";
            continue;
        }

        map<int, int> mp1;
        vector Ve(n, vector<int>());
        for (int i = 0; i < s.size(); i ++) {
            if (!mp1.count(s[i])) {
                mp1[s[i]] = i;
                Ve[i].push_back(i);
            } else {
                Ve[mp1[s[i]]].push_back(i);
            }
        }

        cout << (Ve == ve ? "YES\n" : "NO\n");
    }

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

D. Right Left Wrong

思路

要尽可能地获取最高分,那么最左边的L就要尽可能地去匹配最右边的R,处理出两者的位置,然后用前缀和求和即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    int n;
    cin >> n;

    vector<i64> a(n + 1), pre(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }

    string s;
    cin >> s;
    s = " " + s;

    vector<int> l, r;
    for (int i = n; i >= 1; i --) {
        if (s[i] == 'R') {
            r.push_back(i);
        }
    }

    for (int i = 1; i <= n; i ++) {
        if (s[i] == 'L') {
            l.push_back(i);
        }
    }

    if (l.empty() || r.empty()) {
        cout << 0 << '\n';
        return ;
    }

    i64 ans = 0;
    int idx = 0;
    for (auto i : l) {
        if (r[idx] < i || idx >= r.size())
            break;
        ans += pre[r[idx]] - pre[i - 1];
        idx ++;
    }

    cout << ans << '\n';

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

E. Photoshoot for Gorillas

思路

注意到 $1\le n\cdot m\le 1e5 $,那么可以直接二维差分前缀和处理矩形的覆盖范围,然后将覆盖的值取出从大到小排序和 \(a_i\) 对应即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    i64 n, m, k, w;
    cin >> n >> m >> k >> w;

    vector<i64> a(w);
    for (int i = 0; i < w; i ++) {
        cin >> a[i];
    }

    sort(a.begin(), a.end(), greater<>());


    vector v(n + 2, vector<int>(m + 2));
    auto sum = v;
    for (int i = 1; i + k <= n + 1; i ++) {
        for (int j = 1; j + k <= m + 1; j ++) {
            v[i][j] ++, v[i][j + k] --;
            v[i + k][j]--, v[i + k][j + k]++;
        }
    }

    vector<i64> d;
    for (int i = 1; i <= n; i ++) {
        for (int j = 1; j <= m; j ++) {
            v[i][j] += v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1];
            d.push_back(v[i][j]);
        }
    }

    sort(d.begin(), d.end(), greater<>());

    i64 ans = 0;
    for (int i = 0; i < w; i ++) {
        ans += d[i] * a[i];
    }

    cout << ans << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

F. Color Rows and Columns

思路

一个长为 \(a\),宽为 \(b\) 的矩形可以产生的贡献应该是 \(0,1,2,\dots a+b-3,a+b-2,a+b\),诶?\(a+b-1\) 呢?试想一下,当矩形中只差一个未涂满的情况下,长和宽分别是 \(a-1,b-1\),这个时候产生的贡献为 \(a+b-2\),那么当我们填上这一个点,长和宽就会刚好被涂满,从而产生 \(a+b\) 的贡献,所以 \(a+b-1\) 的情况是不会产生的。

根据以上结论,我们可以处理出每个矩形获得相应点数的代价和贡献,然后从每个矩形中选择一个贡献(不选择以贡献为0的形式展现)出来,然后得到至少为 \(k\) 的最小代价。

没错,这就是分组背包典型模板了,值得注意的是题目中要求的是至少为 \(k\) 的最小代价,也就是说可能存在分数大于 \(k\) 且代价更小的情况,这个时候我们背包的范围就要取大一点,至少也得 \(k+1\)(不要刚好取 \(100\)\(k\) 也可以等于\(100\))。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

	int n, k;
	cin >> n >> k;

	vector w(n + 1, vector<array<i64, 2>>());
	vector<array<int, 2>> a(n + 1);
	for (int j = 1, x, y; j <= n; j ++) {
		cin >> x >> y;
		a[j] = {x, y};
		int tx = x, ty = y;
		for (int i = 1, sum = 0; i <= x + y; i ++) {
			if (i == x + y - 1) continue;
			if (i == x + y) {
				w[j].push_back({i, x * y});
			} else {
				sum += min(tx, ty);
				tx > ty ? tx-- : ty--;
				w[j].push_back({i, sum});
			}
		}
	}

	const int N = 114, M = N - 10;
	vector<i64> dp(N, INT_MAX);

	dp[0] = 0;
	for (int i = 1; i <= n; i ++) {
		for (int j = M; j >= 0; j --) {
			for (auto &[cost , val] : w[i]) {
				if (j < cost) continue;
				dp[j] = min(dp[j], dp[j - cost] + val);
			}
		}
	}

	i64 ans = -1;
	for (int i = k; i <= M; i ++) {
		if (dp[i] != INT_MAX) {
			if (ans == -1) ans = dp[i];
			else ans = min(ans, dp[i]);
		}
	}

	cout << ans << '\n';

}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

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

	return 0;
}

G. Call During the Journey

思路

考虑要找最晚出发的时间,这点我们可以通过二分解决。

然后考虑 Dijkstra,如果当前的时间点采用骑车的话,其花费的时间段和打电话的时间如果有交集,那我们只有两种选择,走路 或者 在原地打完电话之后骑车;若无交集,那就正常骑车即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

struct DIJ {
    using i64 = long long;
    using PII = pair<i64, i64>;
    vector<i64> dis;
    vector<vector<array<i64, 3>>> G;

    int n;
    DIJ() {}
    DIJ(int n_): n(n_) {
        G.resize(n + 1);
    }

    void add(int u, int v, int l1, int l2) {
        G[u].push_back({v, l1, l2});
    }

    void dijkstra(int s, i64 tx, i64 t1, i64 t2) {
        dis.assign(n + 1, 1e18);
        priority_queue<PII> Q;
        dis[s] = tx;
        Q.push({ -dis[s], s});
        while (!Q.empty()) {
            auto [t, u] = Q.top();
            Q.pop();

            t = -t;
            if (dis[u] < t) continue;

            for (auto [v, l1, l2] : G[u]) {
                auto now = t + l2;
                if (max(t, t1) < min(t + l1, t2)) {
                    now = min(now, t2 + l1);
                } else {
                    now = min(now, t + l1);
                }
                if (dis[v] > now) {
                    dis[v] = now;
                    Q.push({ -dis[v], v});
                }
            }
        }
    }
};

void solve() {

    int n, m, t[3] {};
    cin >> n >> m >> t[0] >> t[1] >> t[2];

    DIJ dij(n);
    for (int i = 0; i < m; i ++) {
        int u, v, l1, l2;
        cin >> u >> v >> l1 >> l2;
        dij.add(u, v, l1, l2);
        dij.add(v, u, l1, l2);
    }

    i64 l = 0, r = t[0], ans = -1;
    while (l <= r) {
        i64 mid = l + r >> 1;
        dij.dijkstra(1, mid, t[1], t[2]);
        if (dij.dis[n] <= t[0]) l = mid + 1, ans = mid;
        else r = mid - 1;
    }

    cout << ans << '\n';

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

H. Ksyusha and the Loaded Set

思路

考虑题中所给 \(k\) 的性质,其实就是在集合中从左往右找到第一段两两相差长度至少为 \(k\) 的位置。

所以我们可以维护一个权值线段树,对于集合中出现的数,我们可以在线段树中标记为 \(1\),那么两个 \(1\) 之间相差的 \(0\) 的个数不就等于两数相差的长度吗,那我们只用维护这个区间中最大的 \(0\) 的个数即可,想到这,其实就可以发现这是个比较板的维护 \(0/1\) 串中最大连续 \(0\) 的个数线段树,对于找长度为 \(k\) 的位置,我们可以线段树上二分,因为是从左往右找,所以找完左边还得看下中间是否符合,即判断左区间后缀连续 \(0\ +\)右区间前缀连续 \(0\) 是否 \(\ge k\),最后找右区间。

每次可以维护一个 \(set\) 代表集合中的数,最后将线段树清空,减少每次新开线段树带来的时间和空间上的损耗。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

template<class Node>
struct SegmentTree {
#define lc u<<1
#define rc u<<1|1
    const int n, N;
    vector<Node> tr;

    SegmentTree(): n(0) {}
    SegmentTree(int n_): n(n_), N(n * 4 + 10) {
        tr.reserve(N);
        tr.resize(N);
        build(1, 1, n);
    }

    void build(int u, int l, int r) {
        tr[u].l = l, tr[u].r = r;
        if (l == r) {
            tr[u] = {l, r, 1, 1, 1};
            return ;
        }
        i64 mid = (l + r) >> 1;
        build(lc, l, mid);
        build(rc, mid + 1, r);
        pushup(tr[u], tr[lc], tr[rc]);
    };

    void pushup(Node& U, Node& L, Node& R) { //上传
        U.l = L.l, U.r = R.r;

        if (L.presum == L.r - L.l + 1) {
            U.presum = L.presum + R.presum;
        } else {
            U.presum = L.presum;
        }

        if (R.lastsum == R.r - R.l + 1) {
            U.lastsum = L.lastsum + R.lastsum;
        } else {
            U.lastsum = R.lastsum;
        }

        U.Maxsum = max({L.Maxsum, R.Maxsum, L.lastsum + R.presum});
    }

    void modify(int u, int pos) {
        if (tr[u].l >= pos && tr[u].r <= pos) {
            tr[u].Maxsum ^= 1;
            tr[u].presum = tr[u].lastsum = tr[u].Maxsum;
            return ;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (pos <= mid)
            modify(lc, pos);
        else
            modify(rc, pos);
        pushup(tr[u], tr[lc], tr[rc]);
    }

    int query(int u, int k) { //区查
        if (tr[u].l == tr[u].r)
            return tr[u].l;
        if (tr[lc].Maxsum >= k)
            return query(lc, k);

        if (tr[lc].lastsum + tr[rc].presum >= k) {
            return tr[lc].r - tr[lc].lastsum + 1;
        }

        return query(rc, k);
    }
};

struct Node { //线段树定义
    i64 l, r;
    i64 presum, lastsum, Maxsum;
};

constexpr int N = 2e6 + 10, M = N - 10;
SegmentTree<Node> tr(M);

void solve() {

    set<int> s;

    int n;
    cin >> n;

    for (int i = 1; i <= n; i ++) {
        int x;
        cin >> x;
        s.insert(x);
        tr.modify(1, x);
    }

    int m;
    cin >> m;
    while (m--) {
        char op;
        int x;
        cin >> op >> x;
        if (op == '+') {
            s.insert(x);
            tr.modify(1, x);
        } else if (op == '-') {
            s.erase(x);
            tr.modify(1, x);
        } else {
            if (x > tr.tr[1].Maxsum) {
                cout << M - tr.tr[1].lastsum + 1 << ' ';
            } else {
                cout << tr.query(1, x) << ' ';
            }
        }
    }

    cout << '\n';

    for (auto &x : s) {
        tr.modify(1, x);
    }

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

posted @ 2024-08-15 01:54  Ke_scholar  阅读(449)  评论(0编辑  收藏  举报