重新振作第三天----NBUACMer-Beginnerround10.18题解
题目链接:https://ac.nowcoder.com/acm/contest/93542#question
A:至至子的斐波那契 (预处理+二分)
题意介绍:有T组查询,每组查询给出一个正整数a,要求找出斐波那契数列中,距离a的距离最近的一项的下标,如果距离相同,则输出下标较小的。
数据范围: a<=1E18
思路:
先预处理出斐波那契数列,然后每次通过二分找到大于等于a的第一个数的下标p,那么p-1就是第一个刚好小于a的数的下标,比较两个数字与a之间的距离即可。
复杂度:O(Tlog(n))
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int n,k;
int fib[1000],cnt=0;
void init(){
fib[1]=1;
for(int i=2;;i++){
fib[i]=fib[i-1]+fib[i-2];
if(fib[i]>1E18){
cnt=i;
break;
}
}
}
void solve(){
int x;
cin>>x;
int p=lower_bound(fib+1,fib+cnt,x)-fib;
if((fib[p]-x)>=(x-fib[p-1])){
cout<<p-1<<endl;
}
else cout<<p<<endl;
}
signed main(){
ios;
init();
int t=1;
cin>>t;
while(t--)
solve();
}
B:爱吃素 (数学)
题意介绍:给出T组查询,每组查询给出两个数字a和b,判断a*b是否为质数。
数据范围:a<1E11 b<1E11
思路:两个数相乘为质数的可能只能是其中一个数字为1,另一个数字为质数。由此,我们只有判断是否存在其中一个数字为1,另一个数字为字数即可。
复杂度:O(T*sqrt(n))
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
bool is_prime(int n){
if(n<2)return false;
for(int i=2;i<=n/i;i++){
if(n%i==0)
return false;
}
return true;
}
int n,m;
void solve(){
cin>>n>>m;
if(is_prime(n)&&m==1||is_prime(m)&&n==1){
cout<<"YES"<<endl;
}
else cout<<"NO"<<endl;
}
signed main(){
ios;
int t=1;
cin>>t;
while(t--)
solve();
}
C:皇城PK (思维)
题意介绍:有T组查询,每一组查询先给出两个正整数n和m,代表n个选手和m局比赛。之后m行输入两个整数a,b,代表选手a打败了选手b。一旦有人有了败局,则他一定不可能是冠军。求有多少个选手有可能获得冠军。
数据范围: n<1E5 m<1E5
思路:统计每一个人的败局数,一旦有了败局,则没有可能冠军。如果没有败局,则可能成为冠军。
复杂度:O(T(n+m))
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const int mod=1E9+7;
int n,m,k;
void solve(){
cin>>n>>m;
int a[n+1];
memset(a,0,sizeof a);
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
a[v]++;
}
int ans=0;
for(int i=1;i<=n;i++){
if(a[i])ans++;
}
cout<<n-ans<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}
D:牛牛爱几何 (简单计算几何)
题意介绍:有多组输入,每组输入正方形的边长n,每条圆弧以正方形边长为直径,求下面这个阴影部分的面积。
数据范围: n<1E7
思路: 两个圆形减去正方形即可。
复杂度:O(1)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const double pi=acos(-1);
int n,k;
void solve(){
double x;
while(cin>>x)
cout<<fixed<<setprecision(6)<<(x/2)*(x/2)*pi*2-x*x<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}
E:小红的小红走矩阵 (构造)
题意介绍:太繁琐了,懒得概括,因为这题涉及到另外一道题目的题干,有兴趣的可以自己查。
数据范围: 3<=n,m<=1E3
思路:先根据行列来生成字母,这样的话是每一个都可以走的。接着我们加以限制,使得1到n-1行的1,2两列,没有办法走,只能走第n行,这样只需mp[i][1]=mp[i][2]即可。然后我们不难判断,如果步数最少的话,那就是直接往右走到底,但是这样的话,答案就是n+m-2。我们只能多走几步。但是他多走了就得回来,所以增加的是偶数。我们不妨使得最后的结果是n+m,那么就需要让他在多走两步,即mp[n][m-1]=mp[n][m]。
复杂度:O(nm)
代码实现:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
int n,m;
char mp[maxn][maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
mp[i][j]=(i+j)%26+'a';
for(int i=1;i<n;i++)mp[i][2]=mp[i][1];
mp[n][m-1]=mp[n][m];
for(int i=1;i<=n;i++)cout<<mp[i]+1<<endl;
return 0;
}
F:游戏购买! (BFS)
题意介绍:首先给出n和m表示地图大小,然后给定一个x代表刺激度指标。接下来给出两个坐标(sx,sy)和(ex,ey),代表起点和终点。然后输入n行,每行m个数字,表示地图上每个位置的刺激度。如果值为-1,则代表无法通行。求从终点前往起点,且经过的点存在刺激度大于x的最短距离,如果无法实现则输出-1。
数据范围: 1<=n,m<=2000
思路:从起点bfs一遍,再从终点bfs一遍,分别记录到达的最短距离。然后遍历整个矩阵,将刺激度大于x的值的地方的两次bfs相加求最小值即可。
复杂度:O(n*m)
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m,x;
cin>>n>>m>>x;
int sx,sy,ex,ey;
cin>>sx>>sy>>ex>>ey;
vector<vector<int>>maze(n+1,vector<int>(m+1,0));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>maze[i][j];
}
}
vector<vector<int>>vis(n+1,vector<int>(m+1,0));
vector<vector<int>>vis1(n+1,vector<int>(m+1,0));
queue<pair<int,int>>q;
q.push({sx,sy});
int dx[4]={0,0,-1,1};
int dy[4]={1,-1,0,0};
while(q.size()){
int x=q.front().first;
int y=q.front().second;
q.pop();
for(int i=0;i<4;i++){
if(x+dx[i]>=1&&dx[i]+x<=n&&y+dy[i]<=m&&y+dy[i]>=1&&!vis[x+dx[i]][y+dy[i]]&&maze[x+dx[i]][y+dy[i]]!=-1){
q.push({x+dx[i],y+dy[i]});
vis[x+dx[i]][y+dy[i]]=vis[x][y]+1;
}
}
}
q.push({ex,ey});
while(q.size()){
int x=q.front().first;
int y=q.front().second;
q.pop();
for(int i=0;i<4;i++){
if(x+dx[i]>=1&&dx[i]+x<=n&&y+dy[i]<=m&&y+dy[i]>=1&&!vis1[x+dx[i]][y+dy[i]]&&maze[x+dx[i]][y+dy[i]]!=-1){
q.push({x+dx[i],y+dy[i]});
vis1[x+dx[i]][y+dy[i]]=vis1[x][y]+1;
}
}
}
int ans=99999999;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
//cout<<vis[i][j]<<' ';
if(maze[i][j]>x&&vis[i][j]&&vis1[i][j]){
ans=min(vis[i][j]+vis1[i][j],ans);
}
}
//cout<<endl;
}
if(ans!=99999999)
cout<<ans<<endl;
else cout<<-1<<endl;
}
G:糟糕的打谱员 (dp)
题意介绍:有T组测试数据,首先给出一个n,代表了谱子的长度,然后每行输入两个数字c[i]和a[i],c[i]代表下棋的人,取值只有0和1。a[i]代表的是位置,取值是1-10。一个序列合法的条件是,先后两步的执棋人不同,并且下的位置也不同,求最长合法序列。
数据范围: n<=1E5
思路:典型的dp问题,首先开大小为n的dp数组,记录最后一个步骤是下标为i的步骤时的最长合法序列。然后我们再用一个数组pos[2][10],记录以两个人,分别以1-10结尾时的最长的序列长度。然后,我们遍历序列,对于每一个序列,更新它加在不同位置后面所得到的长度,同时更新dp数组的值。我们对这个值和ans的值取max即可。
复杂度:O(Tn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n;
cin>>n;
vector<int>a(n+1,0);
vector<int>c(n+1,0);
for(int i=1;i<=n;i++){
cin>>c[i]>>a[i];
}
vector<vector<int>>pos(2,vector<int>(11,0));
vector<int>dp(n+1,0);
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=10;j++){
if(a[i]!=j){
dp[i]=max(dp[i],dp[pos[c[i]^1][j]]+1);
}
}
if(dp[i]>dp[pos[c[i]][a[i]]]){
pos[c[i]][a[i]]=i;
}
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
}
int main(){
int t;
cin>>t;
while(t--)
solve();
}
H:牛牛冲钻五 (模拟)
题意介绍:有n组测试案例,每组测试案例先给出两个数字n和k,分别代表比赛场数和连胜奖励的星数。接着给出一个由W和L组成的字符串,表示比赛情况,其中W代表win,L代表lose。
数据范围: 3<=n<=1E5 2<=k<=100
思路:设置一个计数器x,每次获胜则x++,当x>=3的时候,ans+3,否则ans+1。当输的时候,则ans--,同时x重置为0即可。
复杂度:O(Tn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int n,k;
void solve(){
cin>>n>>k;
string s;
int x=0;
int ans=0;
cin>>s;
for(int i=0;i<n;i++){
if(s[i]=='W'){
x++;
if(x>=3){
ans+=k;
}
else ans++;
}
else {
ans--;
x=0;
}
}
cout<<ans<<endl;
}
signed main(){
ios;
int t=1;
cin>>t;
while(t--)
solve();
}
I:我不是酸菜鱼 (思维)
题意介绍:给出n个数字a1到an,这n个数字的积为g,在满足g%2k=0的情况下,求得最大的k。
数据范围: n<=5E6 1<=ai<=215
思路:n个数字相乘肯定会超出数据范围。我们不妨分别求出每一个数字关于2进行质因数分解,求出该数字可以被多少个2整除。之后,在将所有的个数累加,即是最终的答案。
复杂度:O(logn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int n,m,k;
void solve(){
cin>>n;
int x,ans=0;
for(int i=0;i<n;i++){
cin>>x;
while(x%2==0){
x/=2;
ans++;
}
}
cout<<ans<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}
J:四面体 (立体几何)
题意介绍:给出四面体的四个点,计算四面体的体积。
数据范围: [-50,50]
思路:纯纯立体几何题,空间中六面体的体积,先找一个角的三条边,然后其中两条边叉乘的结果再和第三条边点乘即可。建议线性代数好好学。
复杂度:O(1)
代码实现:
#include <iostream>
#include <cmath>
struct Point {
double x, y, z;
};
// 计算向量差
Point vectorDifference(Point a, Point b) {
Point result;
result.x = a.x - b.x;
result.y = a.y - b.y;
result.z = a.z - b.z;
return result;
}
// 计算向量叉积
Point vectorCrossProduct(Point a, Point b) {
Point result;
result.x = a.y * b.z - a.z * b.y;
result.y = a.z * b.x - a.x * b.z;
result.z = a.x * b.y - a.y * b.x;
return result;
}
double vectorDotProduct(Point a, Point b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// 计算四面体体积
double tetrahedronVolume(Point a, Point b, Point c, Point d) {
Point AB = vectorDifference(b, a);
Point AC = vectorDifference(c, a);
Point AD = vectorDifference(d, a);
Point crossProduct = vectorCrossProduct(AC, AD);
double dotProduct = vectorDotProduct(AB, crossProduct);
return std::abs(dotProduct) / 6.0;
}
int main() {
Point A, B, C, D;
std::cin >> A.x >> A.y >> A.z;
std::cin >> B.x >> B.y >> B.z;
std::cin >> C.x >> C.y >> C.z;
std::cin >> D.x >> D.y >> D.z;
double volume = tetrahedronVolume(A, B, C, D);
std::cout << volume << std::endl;
return 0;
}
K:异或和之和 (位运算+数学)
题意介绍:给出一个长度为n的数组,求这些数中任取3个数异或运算后求和的值。
数据范围: 3<=n<=2E5 ai<=1E18
思路:首先,我们是这些数中任意选取,那么就相当于所有数字组合的情况都会发生。这样就保证了每一位的独立性,因为每一位都会与其他数字相同位置上数进行异或,也会遍历过所有的情况。因此,我们只需要统计这个位置上的0与1的个数即可。然后,三个异或为1才会是有效贡献,因为如果为0,其实我们也就没有必要计算,因为对于结果没有影响。所以只存在两种情况,1个1和3个1。对于这两种情况,假设1的个数为x,那么一个1的情况就是x(n-x)(n-x-1)种,三个1的情况为x(x-1)(x-2)种。每一种的值都是v[i],代表这个位置上的1的权值。然后遍历不同位置,求和即可。1E18的话最多应该是60个位置。我们就遍历一遍就行。
复杂度:O(61n)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const int mod=1E9+7;
int ksm(int n,int m,int mod){
int res=1;
n=n%mod;
while(m){
if(m&1) res=res%mod*n%mod;
n=n*n%mod;
m>>=1;
}
return res%mod;
}
int n,m,k;
int bit[62];
int vv[62]={1};
void solve(){
for(int i=1;i<=61;i++){
vv[i]=vv[i-1]*2%mod;
}
cin>>n;
int x=0;
for(int i=1;i<=n;i++){
cin>>x;
for(int j=0;j<=61;j++){
if(x&1){
bit[j]++;
}
x>>=1;
}
}
int ans=0;
for(int i=0;i<=61;i++){
int x=bit[i];
int y=n-x;
ans=(ans+max(x*(x-1)%mod*(x-2)%mod*ksm(6,mod-2,mod)%mod*vv[i]%mod,0ll)+max(x*(y-1)%mod*y%mod*ksm(2,mod-2,mod)%mod*vv[i]%mod,0ll))%mod;
}
cout<<ans<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}
L:完全图 (思维)
题意介绍:有t组数据,每组数据给出n和m,代表一个完全图的顶点数量以及可以删掉的边数。求最多可以有多少个联通分量。
数据范围: 1<=n,m<=1E18
思路:要使得一个n顶点的完全图,多出一个联通分量,那么就需要删掉一个顶点的n-1条边,再多一个就再删n-2条边。我们二分最后一次减去的边的数量,然后用n减去这个值,即可得到最终的答案。但是这边有个处理细节,在二分的时候,参数要改为double,因为会爆long long。
复杂度:O(logn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int n,m,k;
bool check(long double mid){
if((n-mid)*(mid+n-1)/2>m)
return true;
return false;
}
void solve(){
cin>>n>>m;
int l=0,r=n-1,mid;
while(l<r){
mid=(l+r+1)>>1;
if(check(mid)){
l=mid;
}
else r=mid-1;
}
cout<<n-r<<endl;
}
signed main(){
ios;
int t=1;
cin>>t;
while(t--)
solve();
}
M:牛牛走迷宫 (思维)
题意介绍:给出一个n*m的迷宫,要求从(1,1)走到(n,m)。如果可以走到,就走所需步数最小的那一条,如果有多条步数一样,那就输出字典序最小的那一条。输出路径含DLRU,代表往哪里走。
数据范围: 2<=n,m<=500
思路:以DLRU的顺序进行BFS,先抵达的一定是字典序最小的。因为每一层的BFS,所走的步数都是相同的,而我们如果以DLRU的顺序,则会将字典序较小的先放入队列当中。这样可以确保在第一次到达时,是字典序最小的那条。因为本题的数据较小,我就直接存的是字符串。如果数据大的时候,建议使用递归输出路径。
复杂度:O(nm)
代码实现:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
char a[n+1][m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
//cout<<a[i][j];
}
//cout<<endl;
}
int vis[n+1][m+1];
memset(vis,0,sizeof vis);
//cout<<vis[1][2]<<endl;
queue<pair<pair<int,int>,string>>q;
q.push({{1,1},""});
int dx[4]={1,0,0,-1};
int dy[4]={0,-1,1,0};
char ds[4]={'D','L','R','U'};
vis[1][1]=1;
//int cnt=0;
while(q.size()){
int x=q.front().first.first;
int y=q.front().first.second;
string s=q.front().second;
//cout<<s<<endl;
//cnt++;
//cout<<cnt<<endl;
//cout<<s<<endl;
if(x==n&&y==m){
cout<<s.size()<<endl;
cout<<s<<endl;
return 0;
}
q.pop();
//cout<<n<<' '<<m<<endl;
for(int i=0;i<4;i++){
//cout<<x+dx[i]<<' '<<y+dy[i]<<endl;
// cout<<s<<' '<<ds[i]<<endl;
if(x+dx[i]>=1&&y+dy[i]<=m&&x+dx[i]<=n&&y+dy[i]>=1&&!vis[x+dx[i]][y+dy[i]]&&a[x+dx[i]][y+dy[i]]=='0'){
//cout<<i<<' '<<s+ds[i]<<endl;
//cout<<x+dx[i]<<' '<<y+dy[i]<<endl;
vis[x+dx[i]][y+dy[i]]=1;
//cout<<x+dx[i]<<' '<<y+dy[i]<<endl;
q.push({{x+dx[i],y+dy[i]},s+ds[i]});
}
}
}
cout<<-1<<endl;
}
N:疯狂的自我检索者 (思维)
题意介绍:首行输入n,m两个数字,分别代表n个评委和m个隐藏分数的人。然后输入n-m个分数,每个分数都在1-5之间。要求最大可能得分和最小可能得分。
数据范围: 1<= m <= n<=2E5
思路:最大可能得分就是隐藏分数的人都给最大分,最小可能得分就是隐藏分数的人都给最小分。
复杂度:O(n-m)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
const double pi=acos(-1);
int n,m,k;
void solve(){
cin>>n>>m;
int sum=0,x;
for(int i=1;i<=n-m;i++){
cin>>x;
sum+=x;
}
cout<<fixed<<setprecision(6)<<(sum+m)*1.0/n<<' '<<(sum+m*5)*1.0/n<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}
O:小红的环形字符串 (思维)
题意介绍:给出两个字符串,一个是s,一个是t。其中s是一个环,求从s中截取一段连续字串等于字符串t的方案数有多少。
数据范围: 1<=len(t)<=len(s)<=1000
思路:为了解决环形的问题,直接把第一个字符串复制两次,然后直接暴力即可。(substr复杂度为O(n))
复杂度:O(nm)
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int n,k;
void solve(){
string s,t;
cin>>s>>t;
int n=s.size(),m=t.size();
s=s+s;
int ans=0;
for(int i=0;i<n;i++){
string temp=s.substr(i,m);
if(temp==t){
ans++;
//cout<<i<<endl;
}
}
cout<<ans<<endl;
}
signed main(){
ios;
int t=1;
//cin>>t;
while(t--)
solve();
}