牛客小白月赛30 题解

A.黑白边

类似于最小生成树Kruscal算法的思想,用并查集维护当前的连通块直到放入 条边则实现了两两联通,因为要白边最少,排个序就可以了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
struct Edge {
    int u, v, w;
    bool operator < (const Edge &s) const {
        return w < s.w;
    }
}edge[N << 1];
int F[N];
int findz(int x) {
    return F[x] = F[x] == x ? x : findz(F[x]);
}
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, m; cin >> n >> m;
    for(int i = 1; i <= m; i++)    {
        cin >> edge[i].u >> edge[i].v >> edge[i].w;
    }
    sort(edge + 1, edge + 1 + m);
    bool flag = 0;
    for(int i = 1; i <= n; i++) {
        F[i] = i;
    }
    int ans = 0, cnt = 0;
    for(int i = 1; i <= m; i++) {
        int u = edge[i].u, v = edge[i].v;
        int fx = findz(u), fy = findz(v);
        if(fx != fy) {
            F[fx] = fy;
            cnt++;
            if(edge[i].w) ans++;
        }
        if(cnt == n - 1) break;
    }
    if(cnt != n - 1) {
        cout << -1 << endl;
    } else {
        cout << ans << endl;
    }
    return 0;
}   

B. 最好的宝石

太久没写线段树忘了

update: 线段树模板题,多加一个mc表示最大值的个数,然后合并的过程中判断左右子区间的最大值是否相同搞一搞就行了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5 + 5;
typedef long long ll;
struct node {
  int l, r, maxz, mc;
}t[N << 2];
void push_up(int rt) {
  if(t[rt].l == t[rt].r) return ;
  t[rt].maxz = max(t[rt << 1].maxz, t[rt << 1 | 1].maxz);
  t[rt].mc = 0;
  if(t[rt].maxz == t[rt << 1].maxz) {
    t[rt].mc += t[rt << 1].mc;
  }
  if(t[rt].maxz == t[rt << 1 | 1].maxz) {
    t[rt].mc += t[rt << 1 | 1].mc;
  }
}
void build(int rt, int l, int r) {
  t[rt].l = l, t[rt].r = r;
  if(l == r) {
    cin >> t[rt].maxz;
    t[rt].mc = 1;
    return ;
  }
  int mid = l + r >> 1;
  build(rt << 1, l, mid);
  build(rt << 1 | 1, mid + 1, r);
  push_up(rt);
}
void update(int rt, int x, int y) {
  if(t[rt].l == t[rt].r) {
    t[rt].maxz = y;
    return ;
  }
  int mid = t[rt].l + t[rt].r >> 1;
  if(x <= mid) {
    update(rt << 1, x, y);
  } else {
    update(rt << 1 | 1, x, y);
  }
  push_up(rt);
}
pair<int, int> query(int rt, int ql, int qr) {
  if(ql <= t[rt].l && qr >= t[rt].r) {
    return make_pair(t[rt].maxz, t[rt].mc);
  }
  int mid = t[rt].l + t[rt].r >> 1;
  if(qr <= mid) {
    return query(rt << 1, ql, qr);
  } else if(ql > mid) {
    return query(rt << 1 | 1, ql, qr);
  } else {
    auto p1 = query(rt << 1, ql, mid);
    auto p2 = query(rt << 1 | 1, mid + 1, qr);
    if(p1.first == p2.first) {
      return make_pair(p1.first, p1.second + p2.second);
    } else if(p1.first > p2.first) {
      return make_pair(p1.first, p1.second);
    } else {
      return make_pair(p2.first, p2.second);
    }
  }
}
int main() {
  ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
  int n, m; cin >> n >> m;
  build(1, 1, n);
  while(m--) {
    string s; cin >> s;
    if(s == "Ask") {
      int l, r; cin >> l >> r;
      auto tmp = query(1, l, r);
      cout << tmp.first << ' ' << tmp.second << '\n';
    } else {
      int x, y; cin >> x >> y;
      update(1, x, y);
    }
  }
  return 0;
}

C. 滑板上楼梯

先写一个 找到方案数,然后观察答案,发现分布是 1,2,1,2,3,4,3,4, 其中规律的步长为4, 如果是奇数会比偶数少1, 这样我们可以归纳出答案 , 奇数对应减1。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
ll dp[N][2];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    memset(dp, 0x3f, sizeof(dp));
    // dp[3][1] = 1;
    // dp[3][0] = 3;
    // dp[0][0] = 0;
    // for(int i = 1; i <= 20; i++) {
    //     dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + 1;
    //     if(i - 3 >= 0)
    //         dp[i][1] = dp[i - 3][0] + 1;
    // }
    // for(int i = 0; i <= 20; i++) {
    //     cout << i << ' ' << min(dp[i][0], dp[i][1]) << endl;
    // }
    ll n; cin >> n;
    ll now = ((n - 1) / 4 + 1) * 2;
    if(n & 1) cout << now - 1 << endl;
    else cout << now << endl;
    return 0;
}

D.GCD

显然 里所有的素数都要包含在 里面,于是答案就是 ,因为除了素数其他数字都会跟素数组合满足
特判 为 -1,因为全是素数
注意本题把1也认为是素数。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int prime[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n; cin >> n;
    if(n <= 3) {
        cout << -1 << endl;
        return 0;
    }
    for(int i = 2; i <= n; i++) {
        if(!prime[i]) {
            for(int j = 2 * i; j <= n; j += i) {
                prime[j] = 1;
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        ans += (!prime[i]);
    }
    cout << ans + 1 << endl;
    return 0;
}  

E.牛牛的加法

没什么好说的,一开始用 读入了,卡了很久改成 就行了
读入后记得把长度短的前面补0,然后对应位相加取模即可
注意00000、0001的情况,要分别输出0和1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
ll ans[N];
int main() {
    //cout << (80 ^ 34) << endl;
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    string a, b; cin >> a >> b;
    while(a.size() < b.size()) a = '0' + a;
    while(a.size() > b.size()) b = '0' + b;
    vector<int> v1, v2;
    for(auto &x : a) {
        v1.push_back(x - '0');
    }
    for(auto &x : b) {
        v2.push_back(x - '0');
    }
    for(int i = 0; i < v1.size(); i++) {
        ans[i] = (v1[i] + v2[i]) % 10;
        //cout << v1[i] << ' ' << v2[i] << endl;
    }
    int cnt = 0;
    while(ans[cnt] == 0 && cnt < v1.size()) ++cnt;
    //cout << cnt << endl;
    if(cnt == v1.size()) {
        cout << 0 << endl;
        return 0;
    }
    //reverse(ans, ans + max(v1.size(), v2.size()));
    for(int i = cnt; i < v1.size(); i++) {
        cout << ans[i];
    }
    cout << endl;
    return 0;
}  

F.石子合并

每次保留一个最大数字用来合并其他数字,这个最大数字可以有 次贡献( 次合并), 其他的数字分别都有一次贡献,答案为

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
ll a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    //memset(dp, 0x3f, sizeof(dp));
    int n; cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];    
    ll sum = 0, maxz = *max_element(a + 1, a + 1 + n);
    ll cnt = 0;
    for(int i = 1; i <= n; i++) {
        sum += a[i];
    }
    sum -= maxz;
    cout << sum + (n - 1) * maxz << endl;
    return 0;
}

G.滑板比赛

贪心一下,对 个数字排序,从小到大每次在 个数字里 找即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, m; cin >> n >> m;
    multiset<int> st;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        st.insert(a[i]);
    }
    for(int i = 1; i <= m; i++) {
        cin >> a[i];
    }
    sort(a + 1, a + 1 + m);
    int ans = 0;
    for(int i = 1; i <= m; i++) {
        auto pos = st.upper_bound(a[i]);
        if(pos == st.end()) continue;
        ans++;
        st.erase(pos);
    }
    cout << ans << endl;
    return 0;
}  

H.第k小

以为是主席树,但是其实用两个优先队列就能做了,一个大根堆一个小根堆,大根堆里存 个数字,那么小根堆里的堆顶元素就是第 小。注意到插入操作可能会小于小根堆的堆顶,此时的操作是放到大根堆,再从大根堆取出堆顶放入小根堆。
注意元素不够输出-1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n, m, k; cin >> n >> m >> k;
    priority_queue<int, vector<int>, greater<int> > q1;
    priority_queue<int> q2;
    for(int i = 1; i <= n; i++) cin >> a[i], q1.push(a[i]);
    while(q2.size() < k - 1 && !q1.empty()) {
        q2.push(q1.top()), q1.pop();
    }
    for(int i = 1; i <= m; i++) {
        int op; cin >> op;
        if(op == 1) {
            int x; cin >> x;
            if(q2.size() < k - 1) {
                q1.push(x);
                while(q2.size() < k - 1 && !q1.empty()) {
                    q2.push(q1.top()), q1.pop();
                }
            } else if(q1.empty() || q1.top() > x) {
                q2.push(x);
                auto tmp = q2.top(); q2.pop();
                q1.push(tmp);
            } 
        } else {
            while(q2.size() < k - 1 && !q1.empty()) {
                q2.push(q1.top()), q1.pop();
            }
            if(q1.size() && q2.size() == k - 1)
                cout << q1.top() << endl;
            else cout << -1 << endl;
        }
    }
    return 0;
}  

I.区间异或

观察数据范围,先 预处理一下每一个长度异或的最大值
最后每次查询的时候二分查找即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int a[N], ans[N];

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] ^= a[i - 1];
    }
    for(int i = 1; i <= n; i++) {
        for(int j = i; j <= n; j++) {
            ans[j - i + 1] = max(ans[j - i + 1], a[j] ^ a[i - 1]);
        }
    }
    for(int i = 1; i <= n; i++) {
        ans[i] = max(ans[i], ans[i - 1]);
    }
    for(int i = 1; i <= m; i++) {
        int x; cin >> x;
        int p = lower_bound(ans + 1, ans + 1 + n, x) - ans;
        if(p == n + 1) {
                cout << "-1\n";
        }
        else {
                cout << p << "\n";
        }
    }
    return 0;
}

J.小游戏

简单 , 取和不取用0/1表示,显然有转移方程

  • dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]),
  • dp[i][1] = dp[i - 1][0] + 1LL * i * vis[i],其中vis[i]是该数字出现的次数
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
const int mod = 1e9 + 7;
int a[N], vis[N];
ll dp[N][2];
int main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int n; cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        vis[a[i]]++;
    }
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= a[n]; i++) {
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
        dp[i][1] = dp[i - 1][0] + 1LL * i * vis[i]; 
    }
    cout << max(dp[a[n]][0], dp[a[n]][1]) << endl;
    return 0;
}