vp牛客周赛123

27 阅读12分钟

前言

刚考完研究生,回来打打算法复健一下。听说牛客周赛123挺简单的,就来写写看了。

题目列表

图片.png

题目明细

A 小红玩牌

图片.png

思路

简单的条件判断

我的代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n1,n2;
  char c1,c2;
  std::cin >> n1 >> c1 >> n2 >> c2;

  if(n1==n2){
    std::cout << (c1<=c2?"Yes":"No") << "\n";
    return;
  }

  std::cout << (n1>n2?"Yes":"No") << "\n";
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }
  
  return 0;

}

B 小红作弊

图片.png

思路

统计a[i]+b[i]>4的数量即可。

我的代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  std::vector<int> a(13),b(13);

  for(int i = 0; i < 13; i++){
    std::cin >> a[i];
  }
  for(int i = 0; i < 13; i++){
    std::cin >> b[i];
  }

  int ans = 0;
  for(int i = 0; i < 13; i++){
    ans += std::max(0,a[i]+b[i]-4);
  }

  std::cout << ans << "\n";
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }
  
  return 0;

}

C 小红出对

图片.png

思路

相同花色相同数字的牌不论有多少,实际上最多只有一张能打出去,因此贪心地记录第一张的下标,然后一对一对地找能打出去的。

我的代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<std::vector<bool>> vis(200001,std::vector<bool>(4,false));
  std::vector<std::vector<int>> a(200001);
  int ans = 0;
  for(int i = 0; i < n; i++){
    int n;
    char c;
    std::cin >> n >> c;

    if(!vis[n][c-'A']){
      vis[n][c-'A'] = true;
      a[n].push_back(i+1);
    }
  }
  
  for(int i = 1; i <= 200000; i++){
    ans += a[i].size()/2*2;
  }
  std::cout << ans << "\n";
  
  for(int i = 1; i <= 200000; i++){
    for(int j = 0; j+1 < a[i].size(); j+=2){
      std::cout << a[i][j] << " " << a[i][j+1] << "\n";
    }
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

D 小红打牌

图片.png

思路

使用cnt数组储存各个数字出现的次数,想到要枚举a,a,a,a+1,a+1,a+1(即枚举a),但发现需要提前统计b,b和c,c的情况。cnt2表示出现次数大于等于2次的字母数量,cnt4表示出现大于等于4次的字母数量。在枚举到满足上述条件的时候,因为a和a+1比较特殊,先考虑其他情况,共有(cnt2-2)*(cnt2-3)/2种b不等于c的情况,有cnt4-(cnt[a]>=4)-(cnt[a+1]>=4)种b=c的情况。再考虑特殊情况,若cnt[a]>=3+2,共有cnt2-2种b(或c)等于a的情况;若cnt[a+1]>=3+2,共有cnt2-2种b(或c)等于a+1的情况;若cnt[a]>=3+2且cnt[a+1]>=3+2时,共有1种b=a,c=a+1(或c=a,b=a+1)的情况;若cnt[a]>=3+4,共有1种b=c=a的情况;若cnt[a+1]>=3+4,共有1种b=c=a+1的情况。以上全部在每次循环的时候都统计加起来就是最终的答案,当然取余直接用自己的取余模板了。

我的代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

template <const int T> 
struct mod_int {
  const static int _mod = T;
  int _x;

  template <typename P> mod_int(P n = P{}) : _x(n % _mod) {}
  mod_int() : _x(0) {}
  int get_value() { return _x; }
  friend std::istream &operator>>(std::istream &is, mod_int &t) {
    return is >> t._x;
  }
  friend std::ostream &operator<<(std::ostream &os, const mod_int &t) {
    return os << t._x;
  }
  mod_int operator+(const mod_int &a) const {
    int sum = _x + a._x;
    return mod_int(sum >= _mod ? sum - _mod : sum);
  }
  mod_int operator-(const mod_int &a) const {
    int minus = _x - a._x;
    return mod_int(minus >= 0 ? minus : minus + _mod);
  }
  mod_int operator*(const mod_int &a) const {
    i64 mul = 1LL * _x * a._x;
    return mod_int(mul % _mod);
  }
  mod_int operator/(const mod_int &a) const {
    return mod_int(1LL * _x * a.inv() % _mod);
  }
  bool operator==(const mod_int &a) const { return _x == a._x; }
  bool operator!=(const mod_int &a) const { return _x != a._x; }
  bool operator<(const mod_int &a) const { return _x < a._x; }
  bool operator>(const mod_int &a) const { return _x > a._x; }
  bool operator<=(const mod_int &a) const { return _x <= a._x; }
  bool operator>=(const mod_int &a) const { return _x >= a._x; }
  void operator+=(const mod_int &a) {
    _x += a._x;
    if (_x >= _mod) {
      _x -= _mod;
    }
  }
  void operator-=(const mod_int &a) {
    _x -= a._x;
    if (_x < 0) {
      _x += _mod;
    }
  }
  void operator*=(const mod_int &a) { _x = 1LL * _x * a._x % _mod; }
  void operator/=(const mod_int &a) { _x = 1LL * _x * a.inv() % _mod; }
  friend mod_int operator+(int a, const mod_int &b) {
    int sum = a + b._x;
    return mod_int(sum >= _mod ? sum - _mod : sum);
  }
  friend mod_int operator-(int a, const mod_int &b) {
    int minus = a - b._x;
    return mod_int(minus < 0 ? minus + _mod : minus);
  }
  friend mod_int operator*(int a, const mod_int &b) {
    i64 mul = 1LL * a * b._x;
    return mod_int(mul % _mod);
  }
  friend mod_int operator/(int a, const mod_int &b) {
    i64 div = 1LL * a * b.inv();
    return mod_int(div % _mod);
  }

  template <typename Q> 
  mod_int quick_power(Q n) const {
    mod_int res(1), a(_x);
    for (; n; a = a * a) {
      if (n & 1) {
        res = res * a;
      }
      n >>= 1;
    }
    return res;
  }
  mod_int inv() const { 
    return quick_power(_mod - 2);//_mod必须为质数,否则只能用exgcd求inv
  }
};

// using Z = mod_int<P1>;
using Z = mod_int<P2>;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> cnt(200001);
  for(int i = 0; i < n; i++){
    int x;
    std::cin >> x;
    ++cnt[x];
  }
  int cnt2 = 0,cnt4 = 0;
  for(int i = 1; i <= 200000; i++){
    cnt2 += cnt[i]>=2;
    cnt4 += cnt[i]>=4;
  }

  Z ans = 0;
  for(int i = 1; i < 200000; i++){
    if(cnt[i]>=3 && cnt[i+1]>=3){
      int c2 = cnt2-2;
      int c4 = cnt4-(cnt[i]>=4)-(cnt[i+1]>=4);
      ans +=  Z(1LL*c2*(c2-1)/2);
      ans += c4;
      if(cnt[i]>=5){
        ans += c2;
      }
      if(cnt[i+1]>=5){
        ans += c2;
      }
      if(cnt[i]>=5 && cnt[i+1]>=5){
        ans += 1;
      }
      if(cnt[i]>=7){
        ans += 1;
      }
      if(cnt[i+1]>=7){
        ans += 1;
      }
    }
  }

  std::cout << ans << "\n";
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

E 小红出牌(easy)

图片.png

思路

数据量为1e5,想到枚举n,此时需要维护某个信息,然后用递推在每次循环的最后输出答案。观察样例,不难发现,考虑递推时的重点在第四项。i=4时,发现a[i]+1和a[i]-1前面已经出现出现过了,猜想:当a[i]+1和a[i]-1在前面都出现过了,会使得答案减一。观察i=5时,也是如此。此时猜测当a[i]+1和a[i]-1在前面都没出现过,会使得答案加一,而其他情况不会使得答案发生变化。严格证明请自行推演。

我的代码1

O(n)做法

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::vector<bool> vis(n+1);
  int ans = 0;
  for(int i = 0; i < n; i++){
    vis[a[i]] = true;
    if(vis[a[i]+1] && vis[a[i]-1]){
      ans--;
    }else if(!vis[a[i]+1] && !vis[a[i]-1]){
      ans++;
    }
    std::cout << ans << " \n"[i+1==n];
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

我的代码2

O(nlogn)做法

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::set<int> set1;
  std::set<int,std::greater<int>> set2;
  for(int i = 0; i <= n+1; i++){//0和n+1作为哨兵
    set1.insert(i);
    set2.insert(i);
  }

  int ans = 0;
  for(int i = 0; i < n; i++){
    set1.erase(a[i]);
    set2.erase(a[i]);
    if((*set1.lower_bound(a[i])==a[i]+1) && (*set2.lower_bound(a[i])==a[i]-1)){
      ans++;
    }else if((*set1.lower_bound(a[i])!=a[i]+1) &&(*set2.lower_bound(a[i])!=a[i]-1)){
      ans--;
    }
    std::cout << ans << " \n"[i+1==n];
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

F 小红出牌(hard)

图片.png

思路

同上题,但是此题的a[i]可能重复,则需要多考虑些问题,猜测跟前面出现的a[i]+1和a[i]-1的数量有关。使用cnt数组记录数字出现次数,发现当cnt[a[i]+1]>=cnt[a[i]]且cnt[a[i]-1]>=cnt[a[i]]时,答案会减一,同理当cnt[a[i]+1]<cnt[a[i]]且cnt[a[i]-1]<cnt[a[i]]时,答案会加一,其他情况答案不变。

我的代码1

O(n)写法

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::vector<int> cnt(n+1);
  int ans = 0;
  for(int i = 0; i < n; i++){
    cnt[a[i]]++;
    if(cnt[a[i]+1]>=cnt[a[i]] && cnt[a[i]-1]>=cnt[a[i]]){
      ans--;
    }else if(cnt[a[i]+1]<cnt[a[i]] && cnt[a[i]-1]<cnt[a[i]]){
      ans++;
    }
    std::cout << ans << " \n"[i+1==n];
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

我的代码2

O(nlogn)写法,其实此处的multiset是多余的,当时写的时候没想那么多

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::multiset<int> set1;
  std::multiset<int,std::greater<int>> set2;
  set1.insert(-1);
  set1.insert(100002);
  set2.insert(-1);
  set2.insert(100002);

  std::vector<int> cnt(100001);
  int ans = 0;
  for(int i = 0; i < n; i++){
    cnt[a[i]]++;
    set1.insert(a[i]);
    set2.insert(a[i]);

    int t1 = *set1.upper_bound(a[i]);
    int t2 = *set2.upper_bound(a[i]);
    if((t1!=a[i]+1 && t2!=a[i]-1) || 
       (t1==a[i]+1 && cnt[a[i]]>cnt[a[i]+1] && t2!=a[i]-1) || 
       (t1!=a[i]+1 && t2==a[i]-1 && cnt[a[i]]>cnt[a[i]-1]) || 
       (t1==a[i]+1 && cnt[a[i]]>cnt[a[i]+1] && t2==a[i]-1 && cnt[a[i]]>cnt[a[i]-1])){
      ans++;
    }else if(t1==a[i]+1 && cnt[a[i]]<=cnt[a[i]+1] && t2==a[i]-1 && cnt[a[i]]<=cnt[a[i]-1]){
      ans--;
    }

    std::cout << ans << " \n"[i+1==n];
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

G 小红出千

图片.png

思路

首先会有个贪心的思路,假设ai不重复,我们要找到一个区间[low,high],区间长度为n,只要保证这个区间尽可能多地盖住题目给出的ai,记盖住题目最多的数量为max,那么“出千”次数就是n-max。但是我们又不能无脑枚举区间,因为ai的范围是[1,1e9],枚举一定会TLE(超时)。不难发现,要想尽可能盖住更多的ai,至少得盖住其中一个数,那么可以想到枚举每个数,分别将每个数作为区间的左右端点枚举,通过二分(前提是ai有序)找到[ai,ai+n-1]和[ai-n+1,ai]盖住ai的最大值,并更新当前max最大对应的low和high。枚举结束后的n-max就是“出千”次数,接下来就是要输出需要修改的ai下标和修改后的值,最易实现的还是使用set。首先将low和high间的所有值插入set。再扫描一遍ai,找到已经出现的low到high之间的值,再在set中erase掉该值。最后遍历ai,若ai<low或ai>high,此时需要将当前ai“出千”改成当前set中任意值,为方便代码使用,我们替换成set中的第一个值(即set.begin()),输出当前下标和set.begin()。当然由于要输出ai原先的下标,因此排序不能直接对ai做,可以复制一份到bi,对bi排序。

此时的代码
#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::vector<int> b = a;
  std::sort(b.begin(),b.end());

  int low = -1,high = inf1,max = 0;
  for(int i = 0; i < n; i++){
    int idx2 = std::upper_bound(b.begin(),b.end(),a[i]+n-1)-b.begin();
    int idx1 = std::lower_bound(b.begin(),b.end(),a[i])-b.begin();

    if(idx2-idx1>max){
      low = a[i];
      high = a[i]+n-1;
      max = idx2-idx1;
    }
  }
  for(int i = n-1; i >= 0; i--){
    int idx2 = std::upper_bound(b.begin(),b.end(),a[i])-b.begin();
    int idx1 = std::lower_bound(b.begin(),b.end(),a[i]-n+1)-b.begin();

    if(idx2-idx1>max){
      low = a[i]-n+1;
      high = a[i];
      max = idx2-idx1;
    }
  }

  std::cout << n-max << "\n";

  std::set<int> set;
  for(int i = low; i <= high; i++){
    set.insert(i);
  }
  for(int i = 0; i < n; i++){
    if(a[i]>=low && a[i]<=high){
      set.erase(a[i]);
    }
  }

  for(int i = 0; i < n; i++){
    if(a[i]<low || a[i]>high){
      std::cout << i+1 << " " << *set.begin() << "\n";
      set.erase(set.begin());
    }
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

图片.png 图片.png 想了那么多,只能拿10%的分,是因为我们一开始的假设存在问题,题目并没有说ai不重复。因此自然想到去重,其余步骤同上述操作,但是在最后一次遍历准备输出答案的时候,除了遇到ai>low和ai<high的情况要“出千”,实际上在第二次及之后遇到low和high之间的数,也需要对其“出千”。至此,逻辑闭环。

我的代码

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;

constexpr int inf1 = 1E9,inf2 = 0x3f3f3f3f;
constexpr int P1 = 1E9+7, P2 = 998'244'353;

void solve() {
  int n;
  std::cin >> n;

  std::vector<int> a(n);
  for(int i = 0; i < n; i++){
    std::cin >> a[i];
  }

  std::vector<int> b = a;
  std::sort(b.begin(),b.end());

  auto it = std::unique(b.begin(),b.end());

  int low = -1,high = inf1,max = 0;
  for(int i = 0; i < n; i++){
    int idx2 = std::upper_bound(b.begin(),it,a[i]+n-1)-b.begin();
    int idx1 = std::lower_bound(b.begin(),it,a[i])-b.begin();

    if(idx2-idx1>max){
      low = a[i];
      high = a[i]+n-1;
      max = idx2-idx1;
    }
  }
  for(int i = n-1; i >= 0; i--){
    int idx2 = std::upper_bound(b.begin(),it,a[i])-b.begin();
    int idx1 = std::lower_bound(b.begin(),it,a[i]-n+1)-b.begin();

    if(idx2-idx1>max){
      low = a[i]-n+1;
      high = a[i];
      max = idx2-idx1;
    }
  }

  std::cout << n-max << "\n";

  std::set<int> set;
  for(int i = low; i <= high; i++){
    set.insert(i);
  }
  for(auto i = b.begin(); i != it; i++){
    if(*i>=low && *i<=high){
      set.erase(*i);
    }
  }

  std::vector<bool> isok(n+5);//isok[a[i]-low]=true表示a[i]已经不是第一次出现了
  for(int i = 0; i < n; i++){
    if(a[i]<low || a[i]>high){
      std::cout << i+1 << " " << *set.begin() << "\n";
      set.erase(set.begin());
    }else{
      if(set.count(a[i])==0 && isok[a[i]-low]){
        std::cout << i+1 << " " << *set.begin() << "\n";
        set.erase(set.begin());
      }
      isok[a[i]-low] = true;
    }
  }
}

int main() {
  std::cin.tie(nullptr)->sync_with_stdio(false);

  int T = 1;
  // std::cin >> T;
  for (; T--;) {
    solve();
  }

  return 0;
}

后记

如有错误,欢迎大家批评指正。有算法问题或者疑问,也欢迎一起交流或留言。