前言
刚考完研究生,回来打打算法复健一下。听说牛客周赛123挺简单的,就来写写看了。
题目列表
题目明细
A 小红玩牌
思路
简单的条件判断
我的代码
#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 小红作弊
思路
统计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 小红出对
思路
相同花色相同数字的牌不论有多少,实际上最多只有一张能打出去,因此贪心地记录第一张的下标,然后一对一对地找能打出去的。
我的代码
#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 小红打牌
思路
使用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)
思路
数据量为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)
思路
同上题,但是此题的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 小红出千
思路
首先会有个贪心的思路,假设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;
}
想了那么多,只能拿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;
}
后记
如有错误,欢迎大家批评指正。有算法问题或者疑问,也欢迎一起交流或留言。