Contestant'. Rank 847. Rating +31(+81 -50).

又一次成为痛苦号(

A. Musical Puzzle

题意

给定一个字符串,输出所有相邻两个字符组成的不同字符串的个数。

思路

如题,用 \(map\) 即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7;

void solve(){
    int n;
    cin >> n;
    string s;
    cin >> s;
    map<string, int> cnt;
    for(int i=0;i<n - 1;i++){
        cnt[to_string(s[i]) + s[i + 1]] ++;
    }
    cout << cnt.size() << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

题目看半天才看懂,淦

B. Restore the Weather

题意

给定 \(n\) 天的实际温度和预估温度,其中,实际温度按天升序给出,预估温度打乱顺序后给出。

给定整数 \(k\),满足每一天的实际温度和预估温度的差的绝对值不超过 \(k\),输出任意一种满足条件的预估温度的排列。

其中,满足一定有解。

思路

\(k\) 是没有用的。

为何呢?因为满足一定有解,我们升序排序后,依次配对即可,这样就可以让每天的差值最小。

注意配对的时候下标变化。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7;

void solve(){
    int n, k;
    cin >> n >> k;
    vector<pii> a(n);
    vector<int> b(n);
    for(int i=0;i<n;i++){
        cin >> a[i].fs;
        a[i].sc = i;
    }
    for(int i=0;i<n;i++) cin >> b[i];
    sort(all(a)), sort(all(b));
    vector<int> w(n);
    for(int i=0;i<n;i++) w[a[i].sc] = i;
    for(int i=0;i<n;i++){
        cout << b[w[i]] << ' ';
    }
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

注意下标的变换,不要绕晕(

C. Vlad Building Beautiful Array

题意

给定一个序列 \(a\),定义序列 \(b\)\(b_i\)\(a_i\)\(a_i - a_j\) 中任选其一,其中 \(j \in [1, n]\) 可任意选择。

输出能否构造一个奇偶性相同的正整数序列 \(b\)

思路

首先,既然要满足所有元素都是正整数,那么我们升序排序一下。

那么,对于 \(a_i - a_j\)\(j \in [1, i - 1]\)

考虑用 \(a_1\) 作为 \(b_1\),那么如果 \(a_1\) 是奇数,我们一定可以将剩余不是奇数的 \(a_i\) 变为奇数。

如果 \(a_1\) 是偶数,那么其他元素都不可以是奇数,因为我们没办法改变 \(a_1\) 的奇偶性,而奇数减偶数还是奇数。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7;

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    for(int i=0;i<n;i++) cin >> a[i];
    sort(all(a));
    for(int i=1;i<n;i++){
        if(a[i] % 2 != a[0] % 2 && (a[i] - a[0]) % 2 != a[0] % 2){
            cout << "NO\n";
            return;
        }
    }
    cout << "YES\n";
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

搓题解的时候才发现下标居然打错了,还没被fst(((

D. Flipper

题意

给定一个序列 \(a\),定义操作为选定一个区间 \([l, r]\),其中 \(1 \leq l \leq r \leq n\),将整个区间翻转,并将剩余两侧的元素换个位置。

输出操作后最大字典序的序列。

思路

首先,不难发现的是,第一个元素在操作后一定不会在第一个位置,除非只有一个元素。

考虑到字典序的贪心性:如果某一位的两个元素能判定大小,后面的元素都不用考虑。

那么,我们就希望尽可能将最大的元素放在第一位。

由上分析,我们从第二个元素开始,找出最大的元素,并从这个元素开始,将之后的元素全都放到答案的开头。

如上,比对 \(2\ 3\ 1 \ 5\ 4\)\(5\ 4\) 将会放在答案的开头。

其次,区间内一定有一个元素,那么最大的元素的前一个元素一定会在区间中,比如说上述例子的 \(1\) 一定包含在区间中。

接下来我们来考虑左端点的选择:

我们从最大的元素前面的第 \(2\) 个元素开始向前遍历,不难发现我们会按照这个倒序的顺序继续将元素放入答案中,而剩余的元素是按原顺序从序列的开头开始放入的。

因此,我们只需比较当前遍历到的元素是否大于序列中的第一个元素,如果小于,因为我们需要把大的元素放入答案,所以区间就不会覆盖这个元素,我们也就确定了所需的区间。

形象地说,如果剩余的序列是 \(4, 5, 1, 9, 8\),因为 \(8 > 4, 9 > 4, 1 < 4\),所以我们选择 \(9, 8\) 作为区间翻转,\(4, 5, 1\) 按顺序放入,得到 \(8, 9, 4, 5, 1\)

由上,我们会发现第二个样例过不去。

为何呢?因为区间右端点可以是 \(n\),那么可以等效于将区间内的元素翻转并将前面剩余的拼到后面。

那么,如果最大值在最后一个元素,显然我们会出现两个选择:

  1. 区间右端点为 \(n\)

  2. 区间右端点为 \(n - 1\)

也就是说,第二个样例对应的选择是第一个,此时就出现了 \(3 < 4\) 的比较,从而得到了答案。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    int mx = 1;
    for(int i=0;i<n;i++) {
        cin >> a[i];
        if(i > 0 && a[i] > a[mx]) mx = i;
    }
    for(int i=mx;i<n;i++) cout << a[i] << ' ';
    int dist = mx - 1 - (mx == n - 1 ? 0 : 1);
    for(;dist>=0;dist--){
        if(a[dist] < a[0]) break;
    }
    dist = max(dist, 0ll);
    for(int i=mx-1;i>dist;i--) cout << a[i] << ' ';
    for(int i=0;i<=dist;i++) cout << a[i] << ' ';
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

WA on 1.

E. Round Dance

题意

给定 \(n\) 个人,每个人的左右分别有一个邻居(可以是同一个邻居)。每个人只记得自己的一个邻居,并只能和自己的邻居一起跳舞。

令两个跳舞的人之间连一条边,那么如果出现了环,就视为一个 "Round Dance"。

输出 "Round Dance" 的最小和最大个数。

思路

我们将每个人记得的那个邻居之间连一个无向边,构成一个不完全联通的无向图,那么我们可以得到几个连通块。

对照题给条件,既然一个人只能有两个邻居,那么很明显,连通块要么是一整个环,要么是只含有 由两个元素组成的环 的基环树。

如果连通块为一整个环,那么显然这个环中每个人的邻居都是唯一确定的,肯定只能单独作为一个 "Round Dance"。

如果为一个基环树,那么我们一定可以找到一个只确定了一个邻居的人,并把多出的边和他连起来;我们也可以和其他的基环树相连,构成一条链。

因此,最小值就是整环的个数 \(+1\) 并与最大值取最小值,最大值就是连通块的个数。

为了方便遍历,在计算最小值的时候,不妨建一个有向图。

p.s. 这题可以用并查集完成。

时间复杂度:\(O(m)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve(){
    int n;
    cin >> n;
    vector<set<int>> e1(n + 1, set<int>()), e2(n + 1, set<int>());
    for(int i=1;i<=n;i++){
        int cur;
        cin >> cur;
        e1[i].emplace(cur);
        e2[i].emplace(cur);
        e2[cur].emplace(i);
    }
    int mn = 0, mx = 0;
    vector<bool> st1(n + 1);
    auto dfs1 = [&](auto self, int start, int x, int p, int step) -> bool{
        bool res = false;
        for(auto t : e1[x]){
            if(t == p) continue;
            if(st1[t]){
                if(t == start && step > 2) res = true;
                continue;
            }
            st1[t] = true;
            if(self(self, start, t, x, step + 1)) res = true;
        }
        return res;
    };
    for(int i=1;i<=n;i++){
        if(st1[i]) continue;
        st1[i] = true;
        if(dfs1(dfs1, i, i, -1, 1)) mn ++;
    }
    vector<bool> st2(n + 1);
    auto dfs2 = [&](auto self, int x, int p) -> void{
        for(auto t : e2[x]){
            if(t == p || st2[t]) continue;
            st2[t] = true;
            self(self, t, x);
        }
    };
    for(int i=1;i<=n;i++){
        if(st2[i]) continue;
        st2[i] = true;
        dfs2(dfs2, i, -1);
        mx ++;
    }
    cout << mn + (mn == mx ? 0 : 1) << ' ' << mx << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

好久没有出图论题了(

E. Ira and Flamenco

题意

给定一个多重集,以及一个整数 \(m\),输出从多重集中取出 \(m\) 个不同的元素且任意两个元素的差的绝对值都小于 \(m\) 的方案数。

思路

首先,要满足差值的绝对值小于 \(m\),我们就需要满足最大值和最小值的差小于 \(m\)

因此,我们不妨遍历最左边的元素,并以这个元素确定当前能取的元素的范围,然后用组合数求解即可。

当然,这里需要取不同的元素,我们可以用下面的结论:

从一个含 \(n\) 个不同元素的多重集中取 \(m\) 个不同元素的组合数,可以看作从 \(n\) 个不同元素中取 \(m\) 个元素的组合数,再乘以每个元素出现的次数的乘积,即:\(\binom{n}{m} \times \prod_{i=1}^{n}a_i\)

对于范围的确定,我们既可以用滑动窗口,也可以用二分。

组合数需要 \(O(1)\) 计算,因而需要预处理。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

int fact[N], fact_inv[N];

int qp(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

int inv(int n) {
    return qp(n, mod - 2);
}

int C(int n, int m){
    return (fact[n] * fact_inv[n - m] % mod) * fact_inv[m] % mod;
}

void init(){
    fact[0] = fact_inv[0] = 1;
    for(int i=1;i<=2e5;i++){
        fact[i] = fact[i - 1] * i % mod;
        fact_inv[i] = inv(fact[i]);
    }
}

void solve(){
    int n, m;
    cin >> n >> m;
    vector<int> a(n + 1, inf);
    map<int, int> cnt;
    for(int i=0;i<n;i++) cin >> a[i], cnt[a[i]] ++;
    sort(all(a));
    a.resize(n = unique(all(a)) - a.begin());
    vector<int> prod(n);
    prod[0] = cnt[a[0]];
    for(int i=1;i<n;i++) prod[i] = prod[i - 1] * cnt[a[i]] % mod;
    int ans = 0;
    for(int i=0;i<n;i++){
        int k = lower_bound(all(a), a[i] + m) - a.begin() - 1;
        if(k - i < m - 1) continue;
        ans = (ans + cnt[a[i]] * C(k - i, m - 1) % mod * prod[k] % mod * inv(prod[i]) % mod) % mod;
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

这个结论有点抽象

G. Ksyusha and Chinchilla

题意

给定一棵树,将其切割为若干个 三个元素组成的链(如下图),输出切割方案,如果无法切割输出 \(-1\)

思路

首先,既然要让切割尽可能方便,我们肯定会去切割掉连接点尽可能少的边。

因此,我们不妨从叶节点开始向上遍历,找到含有三个元素的父节点,并砍掉父节点上面的边。

这个过程可以用树型 \(dp\) 实现。

具体来说,我们从任意一个顶点开始 \(dfs\),在 \(dp\) 的过程中,传递需要从哪条边到达这个点。

那么,如果出现值为 \(3\) 的情况,我们就把传递过来的这条边砍掉,并把这个点的 \(dp\) 值改为 \(0\)

因而,只要出现值大于 \(3\) 的情况,我们就割不了了。

当然,我们可以一开始预判一下点数是否是 \(3\) 的倍数,不然肯定有多出来的点。

时间复杂度:\(O(n + m)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve(){
    int n;
    cin >> n;
    vector<vector<pii>> e(n + 1);
    for(int i=1;i<n;i++){
        int u, v;
        cin >> u >> v;
        e[u].pb(v, i), e[v].pb(u, i);
    }
    if(n % 3 != 0){
        cout << -1 << '\n';
        return;
    }
    vector<bool> st(n + 1);
    vector<int > dp(n + 1);
    bool f = true;
    set<int> ans;
    auto dfs = [&](auto self, int x, int p, int r) -> void{
        dp[x] = 1;
        for(auto [t, id] : e[x]){
            if(t == p || st[t]) continue;
            st[t] = true;
            self(self, t, x, id);
            dp[x] += dp[t];
        }
        if(dp[x] > 3) f = false;
        else if(dp[x] == 3 && r != -1){
            ans.emplace(r);
            dp[x] = 0;
        }
    };
    st[1] = true;
    dfs(dfs, 1, -1, -1);
    if(!f){
        cout << -1 << '\n';
        return;
    }
    cout << ans.size() << '\n';
    for(auto x : ans) cout << x << ' ';
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

这题怎么比上一题简单多了(