...

[Educational Codeforces Round 166 (Rated for Div

Educational Codeforces Round 166 (Rated for Div. 2)

比赛链接:https://codeforces.com/contest/1976
//也就写写水题骗自己了

A. Verify Password

直接挨个判断秒了

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'
const int mod = 998244353;

void solve() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    s = " " + s + "a";
    int pos = n + 1;
    for (int i = 1; i <= n; i++) {
        if (s[i] >= 'a' && s[i] <= 'z') {
            pos = i;
            break;
        }
    }
    for (int i = 2; i < pos; i++) {
        if (s[i] < s[i - 1]) {
            cout << "NO\n";
            return;
        }
    }
    for (int i = pos + 1; i <= n; i++) {
        if (s[i] < s[i - 1] || (s[i] >= '0' && s[i] <= '9')) {
            cout << "NO\n";
            return;
        }
    }
    cout << "YES\n";
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

B. Increase/Decrease/Copy

​ 观察题目发现只需要添加一个元素到末尾,那么对于前n个位置用min(arr[i],brr[i])作为左端点max(arr[i],brr[i])作为右端点,这一部分答案为区间长度(因为操作不会改变数组相对位置),对于最后一个元素,如果brr数组末位元素介于任何一个区间之中那只需要在原本答案的基础上 + 1 即可,反之则在arr数组和brr数组的前n项中找到其差值最小的值加上即可

​ · 如果$ min(arr[i],brr[i]) \le brr.back() \le max(arr[i],brr[i])arr[i]变为brr[i]的过程中会出现brr.back(),\space+1\space$

​ · 反之则说明不会出现该值,那么则x然后通过加减操作使其相等答案在原本基础上加|brr.back()x|+1

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'

void solve() {
    int n;
    cin >> n;
    vector<int> arr(n + 1), brr(n + 2);
    for (int i = 1; i <= n; i++)cin >> arr[i];
    for (int i = 1; i <= n + 1; i++)cin >> brr[i];
    vector<int> num;
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        int x = arr[i];
        int y = brr[i];
        if (x > y)swap(x, y);
        num.push_back(x);
        num.push_back(y);
        ans += y - x;
    }
    int tmp = inf;
    for (int i = 0; i < 2 * n; i += 2) {
        if (num[i] <= brr.back() && num[i + 1] >= brr.back()) {
            tmp = 1;
            break;
        }
        if (brr.back() < num[i])
            tmp = min(tmp, num[i] - brr.back() + 1);
        if (brr.back() > num[i + 1])
            tmp = min(tmp, brr.back() - num[i + 1] + 1);
    }
    cout << ans + tmp << endl;
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

C. Job Interview

​ 观察题目,说明了如果职位未满员的情况下让能力值更高去做对应岗位,也就是说如果所有人全在的情况下最后一个人一定不会被选到,那么可以先计算出前n+m位员工入职时的总能力值,然后一个一个枚举员工没来,空闲的工作岗位和由于员工没来导致后续人员职位变动

​ 将编程特长的人计入a数组,测试特长的人计入b数组,在枚举时记录两个岗位在职状态从而得到当前没来的人原本从事岗位。

​ 如果当前人员为编程特长且在进行编程工作,如果他没来会让第n + 1位编程特长员工从测试岗位转为编程岗位,此时空余一个测试岗位,让最后一个职工补上

​ 如果当前人员位测试特长但是在进行编程工作可以有两种情况

1.编程特长人员小于n ,此时后面则不会再有编程特长人员,让最后一个职工进行编程工作即可

2.当前员工在编程特长职工中位于n后面,前面已经选取了n位员工进行编程工作,编程工作人员已满后面的职工只能进行测试工作,让最后一个员工参与测试工作即可

​ 如果当前员工为测试特长则同上可以得到工作情况,再次不再赘述

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'
const int mod = 998244353;

void solve() {
    int n, m;
    cin >> n >> m;
    vector<PII > arr(n + m + 2);
    vector<int> a, b;//分别代表编程、测试能力更强的人的编号
    for (int i = 1; i <= n + m + 1; i++)cin >> arr[i].x;
    for (int i = 1; i <= n + m + 1; i++)cin >> arr[i].y;
    int sum = 0;//在不考虑最后一个人的情况下的团队能力值
    for (int i = 1, x = 0, y = 0; i <= n + m + 1; i++) {//x、y分别表示编程、测试在职人员
        if (arr[i].x < arr[i].y) {//测试能力更强
            if (i <= n + m) {
                if (y < m || (x == n)) {//测试人员未满
                    sum += arr[i].y;
                    y++;
                } else {//测试人员已满,只能进行编程工作
                    sum += arr[i].x;
                    x++;
                }
            }
            b.push_back(i);
        } else {
            if (i <= n + m) {
                if (x < n || y == m) {
                    sum += arr[i].x;
                    x++;
                } else {
                    sum += arr[i].y;
                    y++;
                }
            }
            a.push_back(i);
        }
    }
    a.push_back(inf);//加入哨兵防止越界
    b.push_back(inf);
    for (int i = 1, x = 0, y = 0; i <= n + m + 1; i++) {//x、y分别表示编程、测试在职人员
        if (i == n + m + 1) {//最后一个人未到直接输出
            cout << sum << endl;
            return;
        }
        bool ok = true;//是否为编程人员
        if (arr[i].x < arr[i].y) {//如果当前员工来了能进行什么工作
            if (y < m || x == n) {
                y++;
                ok = false;
            } else
                x++;
        }
        if (arr[i].x > arr[i].y) {
            if (x < n || y == m)x++;
            else {
                y++;
                ok = false;
            }
        }
//ok = true说明在该员工后面的位置空闲出了一个编程岗位,反之代表空闲出了一个测试岗位
        int ans = sum;
        if (ok) {
            ans -= arr[i].x;
            int pos = lower_bound(a.begin(), a.end(), i) - a.begin();
            if (a[pos] != i) {//说明该员工测试能力更高,但在进行编程工作说明后面已经不存在编程能力大于测试能力的职工
                ans += arr.back().x;
            } else {
                if (pos <= n) {
                    if (a.size() > n + 1) {//让第n+1为编程特长人员从测试岗位变为编程岗位
                        ans += arr[a[n]].x;
                        ans -= arr[a[n]].y;
                        ans += arr.back().y;
                    } else {//该员工后面已经没有编程特长人员
                        ans += arr.back().x;
                    }
                } else//编程特长人员已经招满
                    ans += arr.back().x;
            }
        } else {
            ans -= arr[i].y;
            int pos = lower_bound(b.begin(), b.end(), i) - b.begin();
            if (b[pos] != i) {
                ans += arr.back().y;
            } else {
                if (pos <= m) {
                    if (b.size() > m + 1) {
                        ans += arr[b[m]].y;
                        ans -= arr[b[m]].x;
                        ans += arr.back().x;
                    } else {
                        ans += arr.back().y;
                    }
                } else
                    ans += arr.back().y;
            }
        }
        cout << ans << ' ';
    }
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

D. Invertible Bracket Sequences

每一个(都会对应一个),那么可以用差分数组来维护状态

​ · s[i]==( sum[i]=sum[i1]+1

​ · s[i]==) sum[i]=sum[i1]1

那么如果任意一个位置存在sum[i]<0则说明不合法。假设翻转区间[l,r]那么区间内的值将会变为sum[i]2(sum[i]sum[l1])02sum[l1]sum[i]可以通过线段树维护求解,但是有更加简单的维护办法如下

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
#define x first
#define y second
#define endl '\n'
const int mod = 998244353;

//将利用差分来维护(为+1,)为-1,如果某个区间翻转之后区间最小值小于零则不可以
void solve() {
    string s;
    cin >> s;
    int n = s.size();
    s = " " + s;
    map<int, int> mp;//记录前缀和为first的左端点有多少个
    int sum = 0, ans = 0;
    mp[sum]++;
    for (int i = 1; i <= n; i++) {//枚举右端点
        if (s[i] == '(')sum++;
        if (s[i] == ')')sum--;
        ans += mp[sum];//加上之前合法的可以反转的左区间
        mp[sum]++;
        while (mp.begin()->first * 2 < sum)mp.erase(mp.begin());//如果之前的左端点前缀和小,已经无法被反转那么就删除掉
//--------------------------------------------------------------------------------------------------------------------
//比如…… 1 2 1 2 3 2 1 ……
//当经过3的时候明显令右边的1与左边两个1相连了,此时将mp[1](mp[1] = 2)给删除掉,表示已经无法去前面i两个1相连形成合法区间了,需要重新统计
//--------------------------------------------------------------------------------------------------------------------
    }
    cout << ans << endl;
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

E. Splittable Permutations

首先通过模拟链表或者二叉树方法得到数组的相对位置,然后降序枚举未出现的数(不会影响后续插入值)。

​ 假设当前已知位置为 6 4 2 5 

​ 那么3可能出现位置为 3 6 3 4 3 2 3 5 3为什么3可以出现在2 4之间呢假设当前数组为6 4 2 3 5那么拆分时可以拆分为

6 4 23 5两个部分且此时并不违背输入

​ 总结一下,对于一个数如果它大于当前要插入的数,那么这个数可以插在它的两侧,也就是提供了两个可选位置,如果这个数小于当前要插入的值时,去观察它左右两个数,如果左边的数大于插值,那么可以插在他的左侧,同理,如果右侧的数大于插值那么可以插在其右侧。但是如果直接对每一个数如此去判断那么一定会出现重复计算。

​ 如何排除重复情况呢?通过上述计算如果左右两个数同时大于插值会产生四个可插入位置x L x x R x显然重复计算了一个位置,那么考虑一个如果min(L,R)大于x说明L R之间多产生了一个位置,那么就可以计算min(L,R)<x的位置有多少个快速得到有多个重复计算的位置

​ 剩余两种情况分别是min(L,R)<x<max(L,R)max(L,R)<x都不会重复计算

​ 由于是降序枚举因此避免了前面插入值影响后续插入值,但是每多插入一个值那么都会多产生一个空位插入所以,每次计算时需要加上之前插入数的次数

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
//#define x first
//#define y second
#define endl '\n'
const int mod = 998244353;

//通过已经确定的位置关系,如果当前插入的值小于该点那么该值可以插在当前位置的左右两边,然后通过找当前已知位置大于该值的个数来去重
//6 3
//6 4 4
//5 5 2
//可以确定位置为 6 4 2 5那么剩余的3可以分别插在5、6和4的两侧,因为可以插在5的左侧,然后分为 2 和 3 4 两个部分
//如此计算得到有6个位置可以选择,明显重复了一个位置,可以通过维护区间最小值,如果区间最小值小于当前值那么说明重复了一个位置,就减掉
//区间最小值为 4 2 2 也就是维护相邻两项的最小值
//由此可以得出实际只有五个位置可以选择

//然后选择插入1的位置,由于是降序枚举那么之前插入的值一定大于当前值
//所以通过插板法可知能够选择的位置一定会多出一种,所以选择方案为原本的5个空位加上新的一个插入的数多出来的一个空位也就是6种
void solve() {
    int n, m;
    cin >> n >> m;
    map<int, int> mp;
    vector<int> arr(m + 1), brr(m + 1);
    for (int i = 1; i <= m; i++) {
        cin >> arr[i];
        mp[arr[i]]++;
    }
    for (int i = 1; i <= m; i++) {
        cin >> brr[i];
        mp[brr[i]]++;
    }
//----------------------------------------------------------------------------------
//通过模拟链表来确认已知的位置关系
    vector<bool> st(n + 1);
    st[n] = true;
    vector<int> nxt(n + 1), pre(n + 1);
    for (int i = 1; i <= m; i++) {
        int l = arr[i], r = brr[i];
        if (st[l]) {
            st[r] = true;
            nxt[r] = nxt[l];
            if (nxt[r])pre[nxt[r]] = r;
            pre[r] = l;
            nxt[l] = r;
        } else {
            st[l] = true;
            pre[l] = pre[r];
            if (pre[l])nxt[pre[l]] = l;
            nxt[l] = r;
            pre[r] = l;
        }
    }
    vector<int> seq;//存储位置关系
    int pos = -1;
    for (int i = 1; i <= n; i++) {
        if (!pre[i] && st[i]) {
            pos = i;
        }
    }
    seq.push_back(pos);
    while (nxt[pos]) {
        pos = nxt[pos];
        seq.push_back(pos);
    }
//----------------------------------------------------------------------------------
    vector<int> a;//存储排序后的seq
    vector<int> b;//存储区间最小值
    for (int i = 1; i < seq.size(); i++)a.push_back(min(seq[i], seq[i - 1]));
    b = seq;
    sort(a.begin(), a.end());
    sort(b.begin(), b.end());
    int len = b.size();
    int ans = 1, tmp = 0;
    for (int i = n; i >= 1; i--) {//先插入比较大的值方便计算
        if (mp[i])continue;
        int x = lower_bound(a.begin(), a.end(), i) - a.begin();
        int y = lower_bound(b.begin(), b.end(), i) - b.begin();
        ans = (ans * (2 * (len - y) - (m - x) + tmp)) % mod;
        tmp++;//已经插入的值的个数,因为是降序遍历所以后面每次插入的时候都会多出一个空位
    }
    cout << ans << endl;
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
//    cin >> t;
    while (t--)
        solve();
    return 0;
}

F. Remove Bridges

​ 题目中说明“对于每一条桥,该桥的下顶点的子树上的所有树边也应该是桥”,由此可知对于第一次加边的一个端点必须为根节点,贪心的想另一个点一定是从根节点出发的最长链的终点。

​ 后续操作贪心的想一定是去选取两条互不相交的最长链相连。由此问题变为了如何将一棵树拆分为互不接触的若干条最长链 进一步得知 需要进行长链剖分来操作,每次选取两条最长的互不接触的链相连。

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define inf INT32_MAX
#define PII pair<int,int>
#define endl '\n'
const int mod = 998244353;

inline void solve() {
    int n;
    cin >> n;
    vector<vector<int>> arr(n + 1);
    for (int i = 1, x, y; i < n; i++) {
        cin >> x >> y;
        arr[x].push_back(y);
        arr[y].push_back(x);
    }
    vector<int> res;
    vector<int> len(n + 1);
    function<void(int, int)> dfs = [&](int x, int fa) {
        for (int y: arr[x]) {
            if (y == fa)continue;
            dfs(y, x);
            len[x] = max(len[x], len[y] + 1);//计算重链
        }
        bool ok = false;
        for (int y: arr[x]) {
            if (y == fa)continue;
            if (len[y] + 1 == len[x] && !ok) {//重链只加入一次
                ok = true;
                continue;
            }
            res.push_back(len[y] + 1);//还需要加上本身
        }
    };

    dfs(1, 0);
    res.push_back(len[1]);//加上根节点重链
    sort(res.begin(), res.end(), greater<int>());//降序
    vector<int> sum(n + 1);
    for (int i = 1; i <= n; i++) {//预处理前缀和
        sum[i] = sum[i - 1];
        if (i <= res.size())sum[i] += res[i - 1];
    }
    for (int i = 1; i < n; i++)
        cout << n - 1 - sum[min(n, 2 * i - 1)] << " \n"[i == n - 1];
}


signed main() {
#ifdef ONLINE_JUDGE
#else
    freopen("test.in", "r", stdin);
    freopen("test.out", "w", stdout);
#endif
    ios::sync_with_stdio(0);
    cout.tie(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}
posted @   让你飞起来  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示