牛客小白月赛100(A,B,C,D,E,F三元环计数)

2024-09-08 04:44

本文主要是介绍牛客小白月赛100(A,B,C,D,E,F三元环计数),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

比赛链接

官方讲解

这场比较简单,ABC都很签到,D是个不太裸需要预处理的 B F S BFS BFS 搜索,E是调和级数暴力枚举,F是三元环计数。三元环考的比较少,没见过可能会偏难。


A ACM中的A题

思路:

就是枚举每个边变成原来的两倍,然后看看两短边之和是否大于第三边即可。

不能只给最短边乘 2 2 2,比如 1 4 8 这组数据,也不能只给第二短边乘 2 2 2,比如 2 3 4 这组数据。不过可以不给最长边乘 2 2 2,因为最长边乘 2 2 2 后,边的长度的顺序没有改变,而原本两短边之和却更不容易大于第三边了。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;ll a[3];
bool check(){ll b[3];memcpy(b,a,sizeof(b));b[0]*=2;sort(b,b+3);if(b[0]+b[1]>b[2])return true;memcpy(b,a,sizeof(b));b[1]*=2;sort(b,b+3);if(b[0]+b[1]>b[2])return true;memcpy(b,a,sizeof(b));b[2]*=2;sort(b,b+3);if(b[0]+b[1]>b[2])return true;else return false;
}int main(){cin>>a[0]>>a[1]>>a[2];sort(a,a+3);if(check())cout<<"Yes";else cout<<"No";return 0;
} 

B ACM中的C题

思路:

我们交换位置只有两种策略,一种是一个已交换的数和一个未交换的数交换,另一种是两个未交换的数交换。显然第二种策略可以让未交换的数量减少的更快,所以从贪心的角度出发,我们每次选择两个未交换的数交换,剩最后一个的时候才用第一种策略。

这样交换的话,交换次数就是 ⌈ n 2 ⌉ \left\lceil\dfrac n2\right\rceil 2n 了。不过需要注意 n = 1 n=1 n=1 的话这个数是不可能交换到其他位置的,这时候无解。

code:

#include <iostream>
#include <cstdio>
using namespace std;int n;int main(){cin>>n;for(int i=1,t;i<=n;i++)cin>>t;if(n==1)cout<<-1<<endl;else cout<<(n+1)/2;return 0;
}

C ACM中的M题

思路:

多了一个只能同类别之间的数可以互换,和上一题其实没什么太大差别,我们对每一个类别分别用上题的策略来换即可。如果某个类别只有一个数,就说明这个数没法换到其他位置,这时候就无解了。

code:

#include <iostream>
#include <cstdio>
#include <map>
using namespace std;int n;
map<int,int> mp;int calc(){int ans=0;for(auto [x,t]:mp)if(t==1)return -1;else ans+=(t+1)/2;return ans;
}int main(){cin>>n;for(int i=1,t;i<=n;i++)cin>>t;for(int j=1,t;j<=n;j++){cin>>t;mp[t]++;}cout<<calc();return 0;
}

D ACM中的AC题

思路:

通过人数比 E E E 少一半,大伙都不喜欢傻逼搜索啊(摊手)。

我们先两人对称走,其中一个人走到终点后,留下另一个人,另一个人再单独走出终点。所以这题一个比较直观的思路就是跑两次 B F S BFS BFS,把前面两人对称走和一个人单独走分别 B F S BFS BFS 一下就行了。

不过这样比较难写。还有就是前一个 B F S BFS BFS 找到的最短路径 如果再加上后一个 B F S BFS BFS 的答案 就不一定是最小的了,所以我们前一个 B F S BFS BFS 不能只看找到的第一个答案,而是需要遍历到所有情况,这样的话第二段的 B F S BFS BFS 就要跑好几次,有可能会超时。

考虑到我们只需要找到最少的步数,而不需要知道中间过程,所以第二段我们只需要知道某个位置到出口最短的距离是多少,这可以直接从所有终点 B F S BFS BFS 到整个地图预处理出来。第一个 B F S BFS BFS 比较好写,就是一个简单的 B F S BFS BFS,不过走到下一个位置的时候需要推出对称位置,再把限制条件再写一遍(比如不能出界,不能走到陷阱,不能走到已经走过的点)。

推出对称点用的是中点坐标公式,假设两个点坐标分别为 ( x 1 , y 1 ) ( x 2 , y 2 ) (x_1,y_1)(x_2,y_2) (x1,y1)(x2,y2),中点坐标为 ( x , y ) (x,y) (x,y),那么就有 x = x 1 + x 2 2 , y = y 1 + y 2 2 x=\dfrac{x_1+x_2}2,y=\dfrac{y_1+y_2}2 x=2x1+x2,y=2y1+y2。而对于中心对称来说,原点和对称点就是上面的两点,对称中心点就是中点,知道中点坐标 ( x , y ) (x,y) (x,y),知道原点坐标 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),那么对称点坐标就是 ( x 2 , y 2 ) = ( 2 x − x 1 , 2 y − y 1 ) (x_2,y_2)=(2x-x_1,2y-y_1) (x2,y2)=(2xx1,2yy1)。我是这样想的:两点坐标加起来就是两倍中点坐标,所以两倍中点减去一个点就是另一个点。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <vector>
#include <array>
using namespace std;
const int maxn=2e3+5;
const int inf=1e9;int n,m,sx,sy;
int fx[]={1,-1,0,0},fy[]={0,0,1,-1};
string mp[maxn];
int dis[maxn][maxn];void init(){vector<vector<bool> > vis(n+1,vector<bool>(m+1,false));memset(dis,0x3f,sizeof(dis));queue<array<int,3> > q;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(mp[i][j]=='@'){q.push({i,j,0});vis[i][j]=true;}while(!q.empty()){auto [ux,uy,d]=q.front();q.pop();dis[ux][uy]=d;for(int i=0,x,y;i<4;i++){x=ux+fx[i];y=uy+fy[i];if(x<1 || x>n || y<1 || y>m)continue;if(mp[x][y]=='#')continue;if(vis[x][y])continue;q.push({x,y,d+1});vis[x][y]=true;}}
}
int bfs(int sx,int sy){int ans=inf;vector<vector<bool> > vis(n+1,vector<bool>(m+1,false));queue<array<int,3> > q;q.push({sx,sy,0});vis[sx][sy]=true;while(!q.empty()){auto [ux,uy,d]=q.front();q.pop();if(mp[ux][uy]=='@'){ans=min(ans,d+dis[2*sx-ux][2*sy-uy]);}for(int i=0,x,y,rx,ry;i<4;i++){x=ux+fx[i];y=uy+fy[i];rx=sx*2-x;ry=sy*2-y;if(x<1 || x>n || y<1 || y>m)continue;if(rx<1 || rx>n || ry<1 || ry>m)continue;if(mp[x][y]=='#' || mp[rx][ry]=='#')continue;if(vis[x][y])continue;q.push({x,y,d+1});vis[x][y]=true;}}return ans==inf?-1:ans;
}int main(){cin>>n>>m>>sx>>sy;for(int i=1;i<=n;i++){cin>>mp[i];mp[i]=" "+mp[i];}init();cout<<bfs(sx,sy);return 0;
}

E ACM中的CM题

思路1(三分):

其实能不能算正解我也不清楚,总感觉很悬,非常粗糙的做法。

可以想到我们一定先提高能力,准备好了之后再排雷,不然你先排雷再提高能力,总次数没有减少,排雷的范围还小,不划算。

我们把能力提高到 m m m,然后算最少的排雷次数,把提高能力和排雷次数加起来就是总共花费的时间。

这个算最少排雷次数可以 O ( n ) O(n) O(n) 来算,也可以 O ( n log ⁡ n ) O(n\log n) O(nlogn)

O ( n ) O(n) O(n) 写法就是从小到大枚举有雷的位置,假设上次排雷的范围是 [ l , r ] [l,r] [l,r],如果这个雷在范围内就跳过(因为在范围内就说明这个雷被排过了),如果不在范围内,就以它的位置为左端点 l l l,开始新一轮的排雷,数一下中间重置了几次 l l l 就是排雷次数。

O ( n log ⁡ n ) O(n\log n) O(nlogn) 的写法是用 s e t set set,把雷的位置存入,每次以未被排过的位置最小的雷开始排,用 u p p e r b o u n d upper_bound upperbound 找到下一个未被排过的雷,跳到最后,跳的次数就是排雷次数。这个方法的时间复杂度准确来说应该是 排雷次数乘以 log ⁡ n \log n logn

我们把能力提高到 m m m m m m 看成自变量,总共花费的时间看成是因变量,这个函数就是一个类似 “单谷” 的函数,因为我们能力设的太小或者太大都会导致花费的时间大大增加,只有处在中间的某个位置的时候花费时间才少。可以想到用三分来解决。

不过这不是一个单纯的单谷函数,而是一个大致形状类似单谷,在局部上可能会“抖动”,那么我们可以把范围缩小到一定程度,然后在一个小范围内暴力地计算答案。

code:

#include <cstdio>
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;int n,a[maxn];int check(int m){//当排雷能力变为m时的花费时间int ans=m;for(int i=1,l,r=0;i<=n;i++){if(r<a[i]){l=a[i];r=l+m;ans++;}}return ans;
}int main(){cin>>n;for(int i=1;i<=n;i++)cin>>a[i];sort(a+1,a+n+1);int l=0,r=2e5,lm,rm;while(l+1000<r){lm=l*2/3+r/3;rm=l/3+r*2/3;if(check(lm)<check(rm))r=rm;else l=lm;
//		cout<<l<<" "<<r<<" "<<check(lm)<<" "<<check(rm)<<endl;}int ans=1e9;for(int i=l;i<=r;i++)ans=min(ans,check(i));cout<<ans<<endl;return 0;
}

思路2(调和级数):

上面说过 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的写法的时间复杂度准确来说应该是 排雷次数乘以 log ⁡ n \log n logn。而当 m m m 比较大的时候,排雷次数就会变少,最坏情况下就是 2 e 5 2e5 2e5 的位置里每个位置都有雷,那么时间复杂度就是 20000 m ∗ log ⁡ n \dfrac{20000}{m}*\log n m20000logn

如果我们暴力地来枚举 m m m,时间复杂度一共是 200000 1 ∗ log ⁡ n + 200000 2 ∗ log ⁡ n + ⋯ + 200000 200000 ∗ log ⁡ n = 200000 log ⁡ n ∗ ( 1 1 + 1 2 + ⋯ + 1 200000 ) \dfrac{200000}1*\log n+\dfrac{200000}2*\log n+\dots+\dfrac{200000}{200000}*\log n=200000\log n*(\dfrac{1}1+\dfrac12+\dots+\dfrac{1}{200000}) 1200000logn+2200000logn++200000200000logn=200000logn(11+21++2000001),括号内的数是调和级数,其和约等于 ln ⁡ 200000 ≈ log ⁡ n \ln 200000\approx\log n ln200000logn,因此时间复杂度变成 = n log ⁡ 2 n =n\log^2 n =nlog2n

所以我们如果用这所谓的 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的写法,就可以直接暴力地算。

code:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=2e5+5;int n;
set<int> S;int check(int m){int ans=m;set<int>::iterator it=S.begin();while(it!=S.end()){it=S.upper_bound(*it+m);ans++;}return ans;
}int main(){cin>>n;for(int i=1,t;i<=n;i++){cin>>t;S.insert(t);}int ans=1e9;for(int i=0;i<=200000;i++)ans=min(ans,check(i));cout<<ans<<endl;return 0;
}

F ACM中的ACM题

思路:

三元环计数,虽然不是用来计数而是用并查集把边加入同一个集合,大体做法是差不多的。三元环计数的讲解可以看 OI wiki

首先可以想到如果有一条边连接了 u x u\ x u x,另一条边连接了 u y u\ y u y,如果想要这两条边同属一个集合,那就必须连接 x y x\ y x y。也就是必须是三元环才能让环上三边同属一个集合。

我们在三元环计数的时候,其实是比较暴力地枚举三元环上的三个点三条边,只是我们是通过按顺序不重复地枚举,导致时间复杂度降为了 O ( n n ) O(n\sqrt n) O(nn )。因为我们枚举出了所有三元环的三边,所以我们用三元环计数的写法,把计数的部分改成用并查集将三边合并成同一个集合,最后看一下所有边是否被合并到了同一个集合即可。

三元环计数在 这场K题 也考过一次。

code:

#include <iostream>
#include <cstdio>
#include <array>
#include <vector>
using namespace std;
const int maxn=2e5+5;int T,n,m;int deg[maxn];
array<int,2> edge[maxn];int head[maxn],ent;
struct edge{int v,id,nxt;
}e[maxn];
void add(int u,int v,int id){e[++ent]={v,id,head[u]};head[u]=ent;
}int vis[maxn];int f[maxn];
void init(){for(int i=1;i<=n;i++)deg[i]=0;for(int i=1;i<=n;i++)head[i]=0;ent=0;for(int i=1;i<=m;i++)f[i]=i;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int x,int y){int fx=find(x),fy=find(y);f[fy]=fx;
}int main(){cin>>T;while(T--){cin>>n>>m;init();for(int i=1;i<=m;i++){auto &[u,v]=edge[i];cin>>u>>v;deg[u]++;deg[v]++;}for(int i=1;i<=m;i++){auto &[u,v]=edge[i];if(deg[u]<deg[v] || (deg[u]==deg[v] && u>v))swap(u,v);add(u,v,i);}for(int u=1;u<=n;u++){for(int i=head[u];i;i=e[i].nxt)vis[e[i].v]=e[i].id;for(int i=head[u],v,id1;i;i=e[i].nxt){v=e[i].v;id1=e[i].id;for(int j=head[v],w,id2;j;j=e[j].nxt){w=e[j].v;id2=e[j].id;if(vis[w]){merge(vis[w],id1);merge(id1,id2);}}}for(int i=head[u];i;i=e[i].nxt)vis[e[i].v]=0;}int cnt=0;for(int i=1;i<=m;i++)if(f[i]==i)cnt++;puts((cnt==1)?"Yes":"No");}return 0;
}

这篇关于牛客小白月赛100(A,B,C,D,E,F三元环计数)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1147146

相关文章

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

【LeetCode热题100】前缀和

这篇博客共记录了8道前缀和算法相关的题目,分别是:【模版】前缀和、【模版】二维前缀和、寻找数组的中心下标、除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和。 #include <iostream>#include <vector>using namespace std;int main() {//1. 读取数据int n = 0, q = 0;ci

YOLOv8/v10+DeepSORT多目标车辆跟踪(车辆检测/跟踪/车辆计数/测速/禁停区域/绘制进出线/绘制禁停区域/车道车辆统计)

01:YOLOv8 + DeepSort 车辆跟踪 该项目利用YOLOv8作为目标检测模型,DeepSort用于多目标跟踪。YOLOv8负责从视频帧中检测出车辆的位置,而DeepSort则负责关联这些检测结果,从而实现车辆的持续跟踪。这种组合使得系统能够在视频流中准确地识别并跟随特定车辆。 02:YOLOv8 + DeepSort 车辆跟踪 + 任意绘制进出线 在此基础上增加了用户

牛客小白月赛100部分题解

比赛地址:牛客小白月赛100_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A.ACM中的A题 #include<bits/stdc++.h>using namespace std;#define ll long long#define ull = unsigned long longvoid solve() {ll a,b,c;cin>>a>>b>

归并排序/计数排序

1:归并排序 1.1:代码 void _MergeSort(int* arr, int left, int right, int* tmp){if (left >= right){return;}int mid = (left + right) / 2; _MergeSort(arr, left, mid, tmp); _MergeSort(arr, mid+1, righ

诺瓦星云校招嵌入式面试题及参考答案(100+面试题、10万字长文)

SPI 通信有哪些内核接口? 在嵌入式系统中,SPI(Serial Peripheral Interface,串行外设接口)通信通常涉及以下内核接口: 时钟控制接口:用于控制 SPI 时钟的频率和相位。通过设置时钟寄存器,可以调整 SPI 通信的速度以适应不同的外设需求。数据发送和接收接口:负责将数据从主机发送到从机以及从从机接收数据到主机。这些接口通常包括数据寄存器,用于存储待发

【CF】C. Glass Carving(二分 + 树状数组 + 优先队列 + 数组计数)

这题简直蛋疼死。。。。。 A了一下午 #include<cstdio>#include<queue>#include<cstring>#include<algorithm>using namespace std;typedef long long LL;const int maxn = 200005;int h,w,n;int C1[maxn],C2[maxn];int

笔试强训,[NOIP2002普及组]过河卒牛客.游游的水果大礼包牛客.买卖股票的最好时机(二)二叉树非递归前序遍历

目录 [NOIP2002普及组]过河卒 牛客.游游的水果大礼包 牛客.买卖股票的最好时机(二) 二叉树非递归前序遍历 [NOIP2002普及组]过河卒 题里面给的提示很有用,那个马的关系,后面就注意,dp需要作为long的类型。 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息publ

多个线程如何轮流输出1到100

多个线程如何轮流输出1到100的值 这个面试问题主要考察如何让线程同步,首先线程同步必会用到的就是互斥锁,互斥锁保证多个线程对数据的同时操作不会出错。但是线程同步还会用到条件变量condition_variable,condition_variable(条件变量)是 C++11 中提供的一种多线程同步机制,它允许一个或多个线程等待另一个线程发出通知,以便能够有效地进行线程同步。 conditi

【最新华为OD机试E卷-支持在线评测】机器人活动区域(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-E/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测,专栏文章质量平均 94 分 最新华为OD机试目录: https://blog.