牛客周赛 Round 71 题解 更新至 F 题

Preface

随便v的一场,这场难度不高呢,感觉有些小水,不如前面几场的难度,反而字符串那题更难一些。

我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.

以下是代码火车头:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;

template<typename T>
void cc(const vector<T> &tem) {
    for (const auto &x: tem) cout << x << ' ';
    cout << endl;
}

template<typename T>
void cc(const T &a) { cout << a << endl; }

template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }

template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }

void cc(const string &s) { cout << s << endl; }

void fileRead() {
#ifdef LOCALL
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
    freopen("D:\\AADVISE\\Clioncode\\untitled2\\out.txt", "w", stdout);
#endif
}

void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }

inline int max(int a, int b) {
    if (a < b) return b;
    return a;
}

inline double max(double a, double b) {
    if (a < b) return b;
    return a;
}

inline int min(int a, int b) {
    if (a < b) return a;
    return b;
}

inline double min(double a, double b) {
    if (a < b) return a;
    return b;
}

void cmax(int &a, const int &b) { if (b > a) a = b; }
void cmin(int &a, const int &b) { if (b < a) a = b; }
void cmin(double &a, const double &b) { if (b < a) a = b; }
void cmax(double &a, const double &b) { if (b > a) a = b; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;

Problem A. 构造A+B

显然,当\(k\)小于等于\(n-1\)的时候就可以,否则就不行。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
 
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n >> m;
        if(m<=n-1) cc("YES");
        else cc("NO");
 
    }
    return 0;
}

Problem B. 宝石手串

直接枚举最相邻的两个颜色相同的位置就好了。

首先写一个\(dfs\)函数,参数\(x,y\),代表\(x\)\(y\)位置相抵消要消耗的数量。

开个\(map\),相同颜色的放里面。然后去判断这两个位置消掉需要的数量,最后的\(res\)\(min\)就好了。

但是需要注意的是,\(vector\)里面只枚举相邻的,那么还要再将开头和结尾的位置放进\(dfs\)函数里判断一下。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
map<int, vec_int> mp;
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
int dfs(int x,int y) {
    if (x > y) swap(x, y);
    return min(y - x - 1, x - 1 + n - y);
}
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    cin >> T;
    while (T--) {
        cin >> n;
        string s;
        cin >> s;
        mp.clear();
 
        rep(i, 1, n) {
            int a;
            a = s[i - 1] - 'a';
            mp[a].push_back(i);
        }
        int ans = INF;
        for (auto &[x,A]: mp) {
            int len = A.size();
            rep(i, 1, len-1) {
                cmin(ans, dfs(A[i - 1], A[i]));
            }
            if (len - 1 > 0) cmin(ans, dfs(A[0], A[len - 1]));
 
        }
        if (ans >= INF / 2) ans = -1;
        cc(ans);
    }
    return 0;
}

Problem C. 最小循环节

有点玄学,观察蒙了一发本质不同字符的数量,然后过了。

首先肯定本质不同的字符肯定需要考虑的,毕竟只能添加不能消掉。然后发现本质不同的字符的数量貌似就是那个极限了。这种题评价就是多做。

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
 
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        map<char,int> mp;
        string s;
        cin >> s;
        for (auto &x: s) {
            mp[x]++;
        }
        int ans = 0;
        for (auto &x: mp) ans++;
        cc(ans);
 
    }
    return 0;
}

Problem D. 气球谜题

首先一个典典\(trick\)就是枚举\(0,1,2\)的位置,然后就是我们倘若固定了\(0,1,2\)的位置之后,我们可以暴力枚举断开的位置,这样子是\(O(n^2)\)的复杂度,那么我们用\(p1,p2,p3\)依次表示前\(i\)项里变成\(0,1,2\)的消耗的总和(就是三个前缀和)。那么此次的\(ans\)就是\(p1_i-p2_i+p2_j-p3_j+p3_n\)\(i<=j\),那么可以用前缀和优化,在枚举的时候取\(p1_i-p2_i\)\(min\)。(这种做法类似于\(O(n)\)复杂度,数组求区间(连续)最大值)。复杂度就降到了\(O(n)\)。具体细节看代码好了。

//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int A[N], D[N];
int p1[N], p2[N], p3[N];
int res;
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
 
void dfs(int q1,int q2,int q3) {
    rep(i, 0, n)
        p1[i] = p2[i] = p3[i] = 0;
 
    rep(i, 1, n) {
        p1[i] += p1[i - 1];
        p2[i] += p2[i - 1];
        p3[i] += p3[i - 1];
        if (A[i] != q1) p1[i] += D[i];
        if (A[i] != q2) p2[i] += D[i];
        if (A[i] != q3) p3[i] += D[i];
        // if (A[i] == q1) p1[i]++;
        // if (A[i] == q2) p2[i]++;
        // if (A[i] == q3) p3[i]++;
    }
 
    int ans = INF;
    int tem = 0;//tem=0是p1[0]-p2[0]的情况
    rep(i, 1, n) {
        cmin(tem, p1[i] - p2[i]);
        cmin(ans, p3[n] + tem + p2[i] - p3[i]);
    }
    cmin(res, ans);
}
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
    while (T--) {
        cin >> n;
        string s;
        cin >> s;
        rep(i, 1, n) cin >> D[i];
        rep(i, 1, n) A[i] = s[i - 1] - '0';
        res = INF;
        rep(i, 0, 2)
            rep(j, 0, 2)
                rep(p, 0, 2) {
                    if (i == j or i == p or j == p) continue;
                    dfs(i, j, p);
                }
        cc(res);
 
    }
    return 0;
}

Problem E. 三角谜题

觉得这个题不如\(D\)题。

枚举腰,然后以\(90\)度为分界线,找到第一个大于\(90\)度的底边和第一个小于\(90\)度的底边。但是实现有一点小恶心。具体细节在代码里面,有注释。

//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
vec_int A;
//--------------------------------------------------------------------------------
//struct or namespace:
 
//--------------------------------------------------------------------------------
 //找到第一个大于90度的底边
int erfen2(int val) {
    int l = -1, r = A.size();
    while (l + 1 != r) {
        int mid = l + r >> 1;
        if (A[mid] < val) l = mid;
        else r = mid;
    }
    return r;
}
 //找到第一个小于90度的底边
int erfen1(int val) {
    int l = -1, r = A.size();
    while (l + 1 != r) {
        int mid = l + r >> 1;
        if (A[mid] <= val) l = mid;
        else r = mid;
    }
    return l;
}
 //计算边长是a,b,c的三角形面积
double dfs(int a,int b,int c) {
    if (a + b <= c or a + c <= b or b + c <= a) return 0;
    double p = (a + b + c) * 1.0 / 2;
    double s = 0;
    s = sqrt(p * (p - a) * (p - b) * (p - c));
    // cc(s);
    return s;
}
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    cin >> T;
    while (T--) {
        cin >> n;
        // set<int> s1, s2;
        map<int,int> s1;
		//s2是个数大于等于2的木棍,用来枚举腰
        set<int> s2;
        A.clear();
 
        rep(i, 1, n) {
            int a, b;
            cin >> a >> b;
            // s1.insert(a);
            s1[a] += b;
            if (s1[a] >= 2) s2.insert(a);
        }
 
        for (auto &[a,b]: s1) A.push_back(a);
 
        double ans = 0;
 
        for (auto &x: s2) {
            int y1 = floor(sqrt(2) * x), y2 = ceil(sqrt(2) * x);
            int l = erfen1(y1), r = erfen2(y2);
            // cc(l, r);

			//这里是防止一个木棍只有2个,找到了自己,这样就需要下标再移一位,下面也同理。
            if (A[l] == x and s1[x] <= 2) l -= 1;
            if (l >= 0) {
                l = A[l];
                cmax(ans, dfs(x, x, l));
            }
 
            if (A[r] == x and s1[x] <= 2) r += 1;
            if (r <= A.size() - 1) {
                r = A[r];
                cmax(ans, dfs(x, x, r));
            }
 
        }
        if (ans <= 0) {
            cc(-1);
        }
        else {
            cout << fixed << setprecision(8) << ans << endl;
        }
 
    }
    return 0;
}

Problem F. 变化的数组

首先能够感觉到,这个数字的变化绝对不会太多,于是我们就在限定的时间复杂度里面尽量的多枚举变化次数就好了。(别的题解提到了变化的次数不会超过\(log\)次,还有详细的证明,这里就不提了,只需要知道不多就好)。

答案的贡献对于每一个数来说其实都是独立的,我们解决每一个数的贡献就好了。

考虑数字\(x\),首先计算出来变化\(i\)次的概率是多少,然后枚举变化的次数,如果和上一次变化的结果是一样的,那么就\(break\),然后自己手动加上剩余的概率所贡献的期望。否则就加上当前的数字\(*\)变化\(i\)次的概率(这就是期望),然后\(x->x+(x \ \ \ and \ \ m)\)

代码有少量注释...

//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
int n, m, T;
int k;
//--------------------------------------------------------------------------------
//struct or namespace:
//板子
namespace ms {
    namespace kuai {
        int kuai(int a, int b) {
            int l = 1;
            while (b) {
                if (b % 2) l = l * a % mod;
                b /= 2;
                a = a * a % mod;
            }
            return l;
        }
 
        int ni(int a) { return kuai(a, mod - 2); }
    }
 
    namespace ni {
        vector<int> fact, infact;
 
        void cal_ni() {
            fact.resize(N + 5);
            infact.resize(N + 5);
            fact[0] = infact[0] = 1;
            infact[1] = 1;
            for (int i = 1; i < N; i++) {
                fact[i] = fact[i - 1] * i % mod;
                if (i >= 2) infact[i] = (mod - mod / i) * infact[mod % i] % mod;
            }
        }
    }
 
    namespace pri {
        vector<int> pri;
        bool ispri[N], biao[N];
 
        void cal_pri() {
            for (int i = 2; i < N; i++) {
                if (biao[i]) continue;
                pri.push_back(i);
                for (int j = i; j < N; j += i) biao[j] = 1;
            }
            for (auto &x: pri) ispri[x] = 1;
        }
    }
 
    namespace gcd {
        int gcd(int a,int b) {
            if (!b) return a;
            return gcd(b, a % b);
        }
    }
}
 
using namespace ms::kuai;
//--------------------------------------------------------------------------------
 
int C(int n,int m) {
    int ans = 1;
    rep(i, 0, m-1) ans *= (n - i), ans %= mod;
    rep(i, 1, m) ans *= ni(i), ans %= mod;
    return ans;
}
 
//计算变化了i次的概率
int cal(int i) {
    return C(k, i) * ni(kuai(2, k) % mod) % mod;
}
 
int A[N];
//处理出来概率放在f数组里面
int f[100];
 
signed main() {
    fileRead();
    kuaidu();
    T = 1;
    //cin >> T;
 
 
    while (T--) {
        cin >> n >> m >> k;
        rep(i, 1, n) {
            cin >> A[i];
        }
 
        rep(i, 0, 70) f[i] = cal(i);
 
        int res = 0;
        rep(i, 1, n) {
 			//fl代表是否计算到了变化了k次的情况
            bool fl = 0;
            int las = -1;
            int x = A[i];
            int pp = 0;
            rep(j, 0, k) {
                if (las == x) {
                    fl = 1;
                    break;
                }
                res += f[j] * x;
                pp += f[j];
 
                pp %= mod;
                res %= mod;
 
                las = x;
                x = x + (x & m);
            }
            //加上剩余的概率
            if (fl) {
                res += (1 - pp + mod) % mod * x % mod;
                res %= mod;
            }
 
        }
        cc(res);
 
    }
    return 0;
}

PostScript

阿巴巴巴阿巴巴

posted @ 2024-12-14 23:13  AdviseDY  阅读(15)  评论(0编辑  收藏  举报