yuwj  

赛时过了AB,想起来有点遗憾啊,做到c的时候12点了早该睡了,不能写了,否则明天起不来了...
但是好像有大佬都没很快过C,我估计也想不到...

AB我就直接贴代码吧,毕竟应该大家都写出来了

点击查看代码
#include <bits/stdc++.h>
using namespace std;

void solve()
{
    int n, pos, mx; cin >> n;
    vector<int> a(n+1);
    set<int> st;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        st.insert(a[i]);
    }
    
    if(st.size() == 1){
        cout << "No\n";
        return;
    }

    cout << "Yes\n";

    mx = a[1], pos = 1;
    for(int i = 2; i <= n; i++){
        if(mx < a[i]) pos = i;
    }

    for(int i = 1; i <= n; i++){
        cout << (i == pos ? 2 : 1) << " \n"[i == n];
    }
}

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

/*
博弈:取石子游戏
1.最后一个人拿走最后一颗石子,败
2.max() - min() <= k,差值 > k 时失败

必胜态,必败态:

必败态1: 
max - min > k+1
2 1 4

只有最初情况会发生,且先手必败,

必败态2:
(sum - n) % 2,
局面:n个1
奇数:先手必败
偶数:先手必胜
*/

#include <bits/stdc++.h>
using namespace std;

void solve()
{
    int n, k, mx = INT_MIN, mn = INT_MAX, cntmx = 0, x, sum = 0; cin >> n >> k;
    vector<int> a(n+1);
    for(int i = 1; i <= n; i++){
        cin >> x, a[i] = x; mn = min(mn, x);
        if(x > mx) mx = x, cntmx = 1;
        else if(x == mx) cntmx++;
        sum += x - 1;
    }

    if(mx - mn > k){
        if(cntmx == 1 && mx - mn - 1 == k);
        else{
            cout << "Jerry\n";
            return;
        }
    }
    
    //cout << sum << '\n';
    if(sum % 2){
        if(n%2)cout << "Jerry\n";
        else cout << "Tom\n";
    }else{
        if(n%2)cout << "Tom\n";
        else cout << "Jerry\n";
    }
}

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

C:最大子段和 + 前后缀分解

这里可以积累两个技巧:
1.最大连续子数组和:
2.数组的前/后缀和最大值

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 1e15;

void solve(){
    ll n, k, mx = 0, curr = 0, L, R; 
    cin >> n >> k;
    string s; cin >> s;
    vector<ll> a(n);
    for(int i = 0; i < n; i++) cin >> a[i];
    
    int pos = -1;
    for(int i = 0; i < n; i++){
        if(s[i] == '0') a[i] = -1e13, pos = i;
    }

    //最大连续子段和:开始新的一段,还是延续?
    for(auto x : a){
        curr = max(curr + x, x); 
        mx = max(curr, mx);
    }

    if(mx > k || (mx != k && pos == -1)){
        cout << "No\n";
        return;
    }

    if(pos != -1){
        mx = 0, curr = 0;
        //ka算法求解连续最大前后缀子段和
        for(int i = pos + 1; i < n; i++){
            curr += a[i]; 
            mx = max(mx, curr);
        }
        R = mx;

        mx = 0, curr = 0;
        for(int i = pos - 1; i>=0; i--){
            curr += a[i];
            mx = max(curr, mx);
        }
        L = mx;

        a[pos] = k - L - R;
    }

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

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

D:树的直径

这里可以积累一个树的直径的“现代”找法
1.dfs找到距离u的最远那个点是什么,深度是多少,设为x
2.然后用这个最深的点,再来一次dfs,就能找到他的最远的点和距离,设为y,d
3.得到整棵树的长直径(d,u,v)-> (d, max(x,y), min(x,y))
翻译成代码:

for(int i = 1; i <= n; i++)
{
  if(!used[i] && !seen[i]){
    auto [d1, j] = dfs(self, i, -1);
    auto [d2, k] = dfs(self, j, -1);
    ans.push_back({d2, max(j, k), min(j, k)});
  }
}

题意:
每次都从树上删除一条路径,存放三元组(d,u,v),按照字典序最大输出结果三元组

思路:
这个题目我第一眼以为树剖一下,维护字典序这样子,但是判错了,还是积累少了
这样思路就很简单了,找呗,标记删除呗,再找直到只有一个结点,结束了

代码:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int t; cin >> t;
    while (t--){
        int n; cin >> n;
        
        vector<vector<int>> g(n + 1);
        for (int i = 1; i < n; i++){
            int u, v; cin >> u >> v;
            
            g[u].push_back(v);
            g[v].push_back(u);
        }
        
        vector <bool> used(n + 1, false), seen(n + 1, false);
        vector <array<int, 3>> ans;
        vector <int> p(n + 1, -1);
        
        while (true){
            if (count(used.begin() + 1, used.end(), false) == 0){
                break;
            }
            seen.assign(n + 1, false);
            
            auto dfs = [&](auto self, int u, int par) -> pair<int, int>{
                pair <int, int> ans = {1, u};
                p[u] = par;
                seen[u] = true;
                
                for (int v : g[u]){
                    if (v != par && !used[v]){
                        auto pi = self(self, v, u);
                        pi.first += 1;
                        ans = max(ans, pi);
                    }
                }
                
                return ans;
            };
            
            for (int i = 1; i <= n; i++) if (!used[i] && !seen[i]){
                auto [d1, j] = dfs(dfs, i, -1);
                auto [d2, k] = dfs(dfs, j, -1);
                ans.push_back({d2, max(j, k), min(j, k)});
                
                while (k != -1){
                    used[k] = true;
                    k = p[k];
                }
            }
        }
        
        sort(ans.begin(), ans.end(), greater<array<int, 3>>());
        for (auto [x, y, z] : ans){
            cout << x << " " << y << " " << z << " ";
        }
        cout << '\n';
    }
    return 0;
}

F1:DP

总得而言,这个dp啊,定义后缀,分段完成转移
题意:
从后往前收集代价,可以交换元素但是付出代价j - i,问最小代价是多少?

思路:
这明显是个dp,
那么,看到dp的首要想法是什么呢?
如果能一直都用最小值都收集就好了,但是这个多出来的 j - i的代价说明事情没有这么简单
如果我用全局最小值去更新,肯定可能会存在其他地方的最小值比我这个位置付出的 j - i的代价更小,并且a[i] == a[i']

那么,全局最小值不可取,那怎么办?
那就用部分最小啊!我用整个全局的第一个的最小值去一直换到第一个位置不就是最优答案了吗?!
因为,设p为最小值位置,那么1,2...p 的 a[i] >= a[p]+1,就算花费一定是最优的(一个个换过去,每次都付出+1的代价)

好的,那么我们就顺利求出了从p到1的最小花费,那么从n到1怎么办?
后面那段就去拿他的最小值更新到我这个位置来呗,
没错,出现了相同操作,可以递归了,不,dp就出现了

于是,
dp[i]:[i,n]后缀的最小花费(从n搬到i的最小花费)
转移我们只要暴力枚举这个最小值p能管多大区间取最小值就好了,很神奇?
dp[i] = min(dp[i], dp[j] + (j-i) * a[p] + (j - p) * 2 + (p - (i+1)))
//dp[i] = [j...n]的最小花费 + 在这个枚举的区间内的最小花费( (j-i) * a[p]) + 移动花费((j - p) * 2 + (p - (i+1))),移到i+1就足够了

这样,我们就在o(n^2)暴力得完成了这个题目

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

void solve()
{
    int n, p; cin >> n;
    vector<int> a(n+1);
    for(int i = 1; i <= n; i++) cin >> a[i];

    vector<ll> dp(n+1, LLONG_MAX);
    dp[n] = 0;
    for(int i = n-1; i >= 0; i--){
        p = i + 1;
        for(int j = i + 1; j <= n; j++)
            if(a[j] < a[p]) p = j;
        
        for(int j = p; j <= n; j++){
            dp[i] = min(dp[i], dp[j] + 1LL * (j - i) * a[p] + 2 * (j - p) + (p - (i+1)));
            //dp[i] = [j...n]的最小花费 + 在这个枚举的区间内的最小花费( (j-i) * a[p]) + 移动花费((j - p) * 2 + (p - (i+1))),移到i+1就足够了
        }
    }

    cout << dp[0] << '\n';
}

int main()
{
    int t; cin >> t;while(t--)
    solve();return 0;
}
posted on 2025-05-06 22:42  xiaowang524  阅读(29)  评论(0)    收藏  举报