前面两道阅读理解直接跳过。
C - Sort
大意
给定一个的排列
,你可以执行最多
次以下操作,让序列变得有序:
- 选择两个元素,交换它们的位置。
输出任意可行的操作次数及其对应的操作步骤。
思路
从
,考虑把
交换到第
位。操作
次后必定有序。
令表示元素
的位置,每次交换
即可。
注意pos的对应位置也要更新。
代码
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
typedef pair<int, int> PII;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n), pos(n);
    vector<PII> ans;
    for(int i = 0, x; i < n; i++){
        cin >> x;
        x--;
        a[i] = x;
        pos[x] = i;
    }
    for(int i = 0; i < n; i++){
        if(a[i] == i) continue;
        ans.push_back({i, pos[i]});
        swap(a[i], a[pos[i]]);
        swap(pos[a[i]], pos[a[pos[i]]]);
    }
    cout << ans.size() << endl;
    for(auto &[x, y]: ans) cout << x + 1 << ' ' << y + 1 << endl;
    return 0;
}D - New Friends
大意
给定一张无向图,若有三个点满足:
- 有边相连 
- 有边相连 
- 没有边相连 
则用一条边连接,问最多能连多少次边。
思路
最终情况下,一个连通块内的任意两点都会连边,即变成一张完全图。
所以bfs得到每个连通块的点数和边数。
设第个连通块有
个点,
条边,则这个连通块对答案的贡献为
。
所有连通块的贡献相加即为答案。
代码
#include <iostream>
#include <queue>
#include <utility>
using namespace std;
#define int long long
typedef pair<int, int> PII;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    vector<vector<int>> G(n);
    for(int i = 0; i < m; i++){
        int a, b;
        cin >> a >> b;
        a--, b--;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    int ans = 0;
    vector<int> vis(n, false);
    auto bfs = [&](int i){
        queue<int> q;
        q.push(i);
        vis[i] = 1;
        int cp = 1, ce = 0;
        while (q.size()) {
            int u = q.front();
            q.pop();
            for (auto v: G[u]) {
                ce++;
                if (vis[v])
                    continue;
                vis[v] = 1;
                q.push(v);
                cp++;
            }
        }
        return PII(cp, ce);
    };
    for(int i = 0; i < n; i++){
        if(vis[i]) continue;
        auto [cp, ce] = bfs(i);
        ans += cp * (cp - 1) - ce;
    }
    ans /= 2;
    cout << ans << endl;
    return 0;
}E - Toward 0
大意
给定,通过两类操作让
变为
:
- 将变为 ,花费 的代价 
- 掷一个色子,等概率掷出中的一个 ,将 变为 ,花费 的代价 
问最优情况下,最小期望花费。
思路
考虑dp。
根据定义,当前的期望值是所有后继情况的期望值的概率加权。
设表示当前数字为
,将其变为
的最小期望花费。
明显。
考虑决策什么,明显是考虑使用操作1还是操作2。
两种操作都会产生一个期望值,期望值中最小的,就是最优决策。
需要分别求出操作1的期望花费和操作2的期望花费
。
- 执行操作1,后继情况只有,因此 。 
- 执行操作2,后继情况有六个,分别是。 
- 到达每种情况的概率都是,可得 。 
- 但是上面情况不能构成状态转移方程,因为时会循环求值。 
- 我们变一下上面的公式,得到真正的。 
得到后,就可以做决策,
。
虽然,但是每次都除以
,最多除
次就会变成
,所以总状态数不多,只有
,不会太大。(大概
级别)。
代码
#include <iostream>
#include <unordered_map>
using namespace std;
#define int long long
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, a, x, y;
    cin >> n >> a >> x >> y;
    unordered_map<int, double> dp;
    auto dfs = [&](auto self, int u) -> double{
        if(u == 0) return 0.0;
        if(dp.count(u)) return dp[u];
        double p = self(self, u / a) + x, q = 0.0;
        for(int i = 2; i <= 6; i++) q += self(self, u / i);
        q = q / 5 + (y * 6.0 / 5.0);
        dp[u] = min(p, q);
        return dp[u];
    };
    dfs(dfs, n);
    
    cout.precision(10);
    cout << fixed << dp[n] << endl;
    return 0;
} F - Transpose
F - Transpose
 
大意
给定一个字符串,其中包括括号和大小写字母。
依次处理每个匹配的括号里的字符,将其左右颠倒,并将大小写字母变换。
问最终的字符串。
思路
首先设表示
对应括号的另一个端点的编号。使用括号匹配的方法预处理出
。
由于题目中的操作是反反得正的(不考虑括号来说),例如,所以我们可以预先确定字符的大小写:
- 括号深度为偶数的字符不会大小写反转。
- 括号深度为奇数的字符则大小写反转。
根据这一点提前交换字母的大小写,就无需再考虑字母的大小写了。
这样我们就简化了问题,接下来设为
按要求处理后的答案(
表示正序或反序)。
那么就可以按表示的顺序输出
中的字符,遇到形如
的子串递归处理即可。
注意反序处理时,先遇到的是右括号。
Q&A
如何找到形如(...)的子串?
在已经预处理
的情况下:
- 如果是正序处理,那么当
时,就可以找到一个范围为
的子串。
- 如果是反序处理,那么当
时,就可以找到一个范围为
的子串。
这就说明了上文的最后一句话。
其他需要注意的点
注意递归调用时不能带上括号,同时
要取反。
而且遇到(...)子串时,需要处理完子串后才能继续处理下一个字符。
例如:
- 正序:应调用
- 反序:应调用
取反是因为反序的时候,(...)子串内的字符需要维持和外面相反的顺序。正序同理。
代码
#include <iostream>
#include <vector>
using namespace std;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    string s;
    cin >> s;
    int n = s.size();
    vector<int> mch(n, -1), stk;
    auto flip = [&](char c) -> char{
        if(c >= 'A' && c <= 'Z') return tolower(c);
        return toupper(c);
    };
    int dep = 0;
    for(int i = 0; i < n; i++){
        if(s[i] == '('){
            stk.push_back(i);
            dep++;
        }
        else if(s[i] == ')'){
            int f = stk.back();
            mch[i] = f; mch[f] = i;
            stk.pop_back();
            dep--;
        }else if(dep & 1) s[i] = flip(s[i]);
    }
    string ans;
    auto dfs = [&](auto self, int l, int r, int d) -> void{
        if(d == 0){
            while(l <= r){
                if(s[l] == '('){
                    self(self, l + 1, mch[l] - 1, 1);
                    l = mch[l];
                }else ans.push_back(s[l]);
                l++;
            }
        }else{
            while(l <= r){
                if(s[r] == ')'){
                    self(self, mch[r] + 1, r - 1, 0);
                    r = mch[r];
                }else ans.push_back(s[r]);
                r--;
            }
        }
    };
    dfs(dfs, 0, n - 1, 0);
    cout << ans << endl;
    return 0;
}


















