Codeforces Round 923 (Div. 3) 题解 (A - F)

蒟蒻刚开始写题解,写得不好请见谅

算法交流群qq:891869943   欢迎进来一起玩~

A

找到最右边的 B 和最左边的 B 即可

void work(){
    cin >> n;
    string s; cin >> s;
    cout << s.rfind('B') - s.find('B') + 1 << '\n';
}

B

题意:i 位置上的字母是第 a[ i ] + 1 次出现

我们直接让每个次数起始都为'a'

然后遍历数组,将目前次数的字符输出,同时变成下一个字母即可

void work(){
    cin >> n;
    vector<int> cnt(n, 'a');

    for(int i = 0; i < n; i++){
        int x; cin >> x;
        cout << char(cnt[x]);
        cnt[x]++;
    }
    cout << '\n';
}

C

对于1到k的每个数字,分以下四种情况

①:a中,b中都有,此时这个数字在哪选都可以

②:只有a有,我们只能从a中选这个数字,用一个cnt1记录这个数量

③:只有b有,我们只能从b中选这个数字,用一个cnt2记录这个数量

④:a, b都没有,此时无解

根据题意,我们知道cnt1 和 cnt2 <= k / 2

const int N = 2e5 + 10;
ll n, m, k;
int a[N], b[N];

void work(){
    cin >> n >> m >> k;
    //哈希表
    vector<bool> has1(k + 1, false);
    vector<bool> has2(k + 1, false);

    for(int i = 1; i <= n; i++){
        cin >> a[i];
        if(a[i] <= k) has1[a[i]] = true;
    } 

    for(int i = 1; i <= m; i++){
        cin >> b[i];
        if(b[i] <= k) has2[b[i]] = true;
    } 

    int cnt1 = 0, cnt2 = 0;
    for(int i = 1; i <= k; i++){
        if(has1[i] && !has2[i]) cnt1++;
        else if(!has1[i] && has2[i]) cnt2++;
        else if(!has1[i] && !has2[i]){
            cout << "NO" << '\n';
            return;
        }
    }

    if(cnt1 <= k / 2 && cnt2 <= k / 2) cout << "YES" << '\n';
    else cout << "NO" << '\n';
}

D

记录两个数组s1,s2

s1[ i ]为下一个大于a[i]的位置,s2[ i ]为下一个小于a[ i ]的位置,显然可以用单调栈记录

对于每次查询[l, r],我们只要查询s1[ l ], 或者 s2[ l ] 是否小于等于 r 即可

const int N = 2e5 + 10;
ll n;
int a[N];
int q[N]; //数组模拟栈

void work(){
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    vector<int> s1(n + 3, n + 1);
    vector<int> s2(n + 3, n + 1);

    int t = 0; //栈指针

    //单调栈
    for(int i = n; i > 0; i--){
        while(t && a[q[t]] >= a[i]) t--;
        if(t) s1[i] = q[t];
        q[++t] = i;
    }

    t = 0; //指针记得清零
    for(int i = n; i > 0; i--){
        while(t && a[q[t]] <= a[i]) t--;
        if(t) s2[i] = q[t];
        q[++t] = i;
    }

    int m; cin >> m;
    while(m--){
        int l, r; cin >> l >> r;
        int R = min(s1[l], s2[l]);

        if(l < R && R <= r) cout << l << " " << R << '\n';
        else cout << "-1 -1" << '\n';
    }
    cout << '\n';
}

还有一位大佬群友的做法: 用set记录每个相邻数字不相同的下标,然后每次查询直接二分查找是否存在[ l , r ]范围的下标即可

const int N = 2e5 + 10;
ll n;
int a[N];

void work(){
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    set<int> s;
    for(int i = 1; i < n; i++){
        if(a[i] != a[i + 1]) s.insert(i);
    }

    int m; cin >> m;
    while(m--){
        int l, r; cin >> l >> r;
        auto pos = s.lower_bound(l);
        if(pos != s.end() && *pos < r) cout << *pos << " " << *pos + 1 << "\n";
        else cout << "-1 -1" << '\n';
    }
    cout << '\n';
}

E

有一个比较容易发现的性质 abs( a[ i + k ] - a[ i ] ) <= 1;

那我们直接从数字1开始填充, 下标从1开始,每次加k,直到大于n后再从下标2继续开始,以此类推

以 n = 10 k = 4 为例 :

不过我们很快发现一个问题,前面4项和后面4项的和差了很多,不符合题意

因此我们不能只是递增填充,还需要递减填充来“中和”一下

我们这样:如果下标从奇数开始,我们就递增填充;如果是偶数,就递减填充

就变成这样

这样我们就可以组成一个合法的序列了

void work(){
    cin >> n >> k;
    int l = 1, r = n;
    int p = 1;
    
    while(p <= k){
        int j = p;
        if(p & 1){
            while(j <= n){
                a[j] = l;
                l++;
                j += k;
            }
        }else{
            while(j <= n){
                a[j] = r;
                r--;
                j += k;
            }
        }
        p++;
    }

    for(int i = 1; i <= n; i++) cout << a[i] << " \n"[i == n]; 
}

F

有 tarjan 和 并查集 2种做法,这里介绍并查集做法

做法主要是判断当前便边是否成环

我们将边 按长度从大到小排序,然后逐步加边

Tips:为什么是从大到小?

举个例子:假如目前有5条边成环,长度分别是1 2 3 4 5,我们从大到小的话逐步加边就是 5 → 4 → 3 → 2 → 1,加 1 的时候发现成环了,此时 1 就是这个环中最小的边。

因此,我们从大到小加边,并且用并查集来判断是否成环

这样我们可以得到所有环中最小的那条边,以及最小边的两个端点u, v

这样我们直接以 u 为根节点, v 为父节点进行dfs,搜到 v 时直接用栈存储返回路径即可

const int N = 2e5 + 10;
ll n, m;
 
//并查集模板
struct DSU {
    std::vector<int> f, sz;
    
    DSU() {}
    DSU(int n) { init(n);}
    
    void init(int n) {
        f.resize(n + 1);
        std::iota(f.begin(), f.end(), 0);
        sz.assign(n + 1, 1);
    }
    
    int find(int x) {
        if(x == f[x]) return x;
        return f[x] = find(f[x]);
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) return false;
        sz[x] += sz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return sz[find(x)];
    }
};
 
struct node{
    int u, v, w;
 
    bool operator<(node b) const{
        return w > b.w;
    }
}e[N];
 
vector<int> adj[N];
int s[N];
int t = 0;
bool ok = false;
bool st[N];
 
//x为目标结点
void dfs(int u, int fa, int x){
    //加个st数组标记路过的结点,防止进入死循环
    st[u] = 1;
    for(auto v : adj[u]){
        if(v == fa) continue;
        if(st[v]) continue;
        
        if(v == x){
            //找到目标结点直接返回
            ok = true;
            s[++t] = u;
            return;
        }
        if(!ok) dfs(v, u, x);
        if(ok){
            s[++t] = u;
            return;
        }
    }
};
 
void work(){
    cin >> n >> m;
    DSU dsu(n);
 
    for(int i = 1; i <= m; i++){
        int u, v, w;
        cin >> u >> v >> w;
        e[i] = node{u, v, w};
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
 
    sort(e + 1, e + m + 1);
 
    int res = 1e9, x1 = 0, x2 = 0;
    for(int i = 1; i <= m; i++){
        auto [u, v, w] = e[i]; 
        if(dsu.same(u, v)){
            res = w;
            x1 = u;
            x2 = v;
        }else{
            dsu.merge(u, v);
        }
    }
 
    s[++t] = x2;
    dfs(x1, x2, x2);
 
    cout << res << " " << t << '\n';
    for(int i = 1; i <= t; i++) cout << s[i] << " \n"[i == t];
 
    ok = false;
    t = 0;
    for(int i = 1; i <= n; i++) {
        adj[i].clear();
        st[i] = false;
    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值