2024牛客算法冬令营第一次比赛全题解

前言

本篇博客是2024牛客算法冬令营第一次训练赛的全题解,包括题目:

  • A.DFS搜索
  • B.关鸡
  • C.按闹分配
  • D.数组成鸡
  • E.本题又主要考察了贪心
  • F.鸡数题
  • G.why买外卖
  • H.01背包,但是bit
  • I .It’s bertrand paradox. Again!
  • J.又鸟之亦心
  • K.牛镇公务员考试
  • L.要有光
  • M.牛客老粉才知道的秘密

A.DFS搜索

1.题目描述

最近,fried-chicken完全学明白了DFS搜索(如上图所示)!于是学弟向他请教DFS搜索,fried-chicken热心的进行了讲解:

所谓DFS搜索,就是给定一个字符串s,问能否找到s的一个子序列,使得该子序列的值为 DFS 或 dfs。

请你分别判断字符串s中是否含有 DFS 子序列与 dfs 子序列。

子序列的定义:从原字符串中选择一些字符,将这些字符按照其在原串中的顺序拼接起来,得到的就是原字符串的一个子序列。例如:ABCDA的子序列可以为ACA、ABCDA、BA等等,但不能为ABE、CBA、AAD。

输入描述:

输入的第一行包括一个正整数T(1≤T≤100),表示测试用例的组数。

对每组测试用例,第一行是一个正整数n(1≤n≤50),表示输入字符串的长度。第二行是一个长度为n的字符串s,保证字符串中只含有英语小写字母与英语大写字母。

输出描述:

对于每组测试用例,输出空格分隔的两个数字,第一个数字表示是否含有 DFS 子序列,第二个数字表示是否含有 dfs 子序列。输出 1 表示含有,输出 0 表示不含有。

输入

5
6
dafasa
6
dDFfSs
6
sfdDSF
6
DFSDFS
3
dfs

输出

0 1
1 1
0 0
1 0
0 1

2.题解

  • 循环遍历一遍,用两个标志flag1和flag2分别记录"DFS"和"dfs"情况即可
#include<bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int x;
        cin >> x;
        char a[x];
        cin >> a;
        int flag1=0;
        int flag2=0;
        for(int i=0;i<x;i++)
        {
            //判断DFS的存在
            if(flag1==0)
            {
                if(a[i]=='D') flag1++;
            }
            else if(flag1==1)
            {
                if(a[i]=='F') flag1++;
            }
            else if(flag1==2)
            {
                if(a[i]=='S') flag1++;
            }
            
            //判断dfs的存在
            if(flag2==0)
            {
                if(a[i]=='d') flag2++;
            }
            else if(flag2==1)
            {
                if(a[i]=='f') flag2++;
            }
            else if(flag2==2)
            {
                if(a[i]=='s') flag2++;
            }
        }
        
        if(flag1==3) cout << 1 <<' ';
        else cout << 0 << ' ';
        if(flag2==3) cout << 1 << endl;
        else cout << 0 << endl;
    }
}

B.关鸡

1.题目描述

如图所示,在一条宽为2、长为2×109+1的管道中,有一只鸡和若干着火点,鸡可以上下左右移动一格、不能出管道上下边界、不能进入着火地点。

鸡初始在(1,0)处,现在给出若干个着火点的坐标,请求出为了不让鸡逃出管道(即到达管道最左端或最右端),最少需要添加多少个着火点。

输入描述:

输入第一行包括一个整数T(1≤T≤104),用例组数。

每组用例第一行包括一个整数n(0≤n≤105),着火点的个数。

随后的第iii行输入两个整数r,c(1≤r≤2,−109≤c≤109),表示着火点的坐标(r,c),坐标含义如题图所示。保证着火点不会为(1,0)。

保证所有数据的n之和Σn≤105,保证同一组用例输入中没有重复的着火点坐标。

输出描述:

对每组样例,输出一个整数,表示最少需要添加多少个着火点才能使得鸡逃不出管道。

输入

8
1
2 0
4
1 3
2 4
1 -2
2 -2
2
1 4
1 5
2
1 -2
2 4
1
1 -1
5
2 0
1 3
2 2
2 4
2 5
9
1 -5
2 2
2 -3
1 -4
1 -1
1 -3
1 -2
2 -4
2 3
0

输出

2
0
3
2
2
1
1
3

2.题解

  • 这题需要分情况讨论
  • 第一种不特殊考虑(1,-1)(2,0)(1,1)这三个特殊点,将左右分开计算,再将左右需要添加的点累加起来构成第一种情况的答案
  • 第二种单独考虑这三个特殊点,3减去特殊点的个数就是第二种情况的答案
  • 两种情况取最小值即可
  • s.count({r^3,c+i})解释:看s中是否存在与(r,c)点可以构成一堵墙的点,r异或是取另一个行(因为二进制1异或3是2,2异或3是1),c加i(-1,1,0)是列左右平移或者不变
  • 运用了set<pair<int,int>>s的相关知识:set会自动帮你按升序排列,先比较first,再比较second
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N=1e6;

void solve()
{
    int n;
    cin>>n;
    set<pair<int,int>>s;
    
    //先将左边和右边分开计算
    int  ans0=2,ans1=2;//初始为左边和右边都为空,需要每边添两个
    
    //若左边或右边出现,那么只需添1个着火点
    for(int i=0;i<n;i++)
    {
        int r,c;
        cin>>r>>c;
        s.insert({r,c});
        
        //注意两个都要添加等于号
        if(c<=0)
        {
            ans0=1;
        }
        if(c>=0)
        {
            ans1=1;
        }
    }
 
    for(auto[r,c]:s)
    {
        for(int i=-1;i<=1;i++)
        {
            if(s.count({r^3,c+i})) //异或3是取另一个行,加i是列左右平移或者不变
            {
                if(c<0) ans0=0;
                if(c>0) ans1=0;
            }
        }
    }
    
    //单独考虑三个特殊点
    int ans=3-s.count({2,0})-s.count({1,-1})-s.count({1,1});
    
    cout<<min(ans,ans0+ans1)<<'\n';
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int T;
    cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

C.按闹分配

1.题目描述

办事大厅目前有n个人和一个办事窗口,每个人都要在这个窗口办事,第i个人办事所需时间为ti​。

时刻0所有人都进入办事大厅,第i个人的不满意度Di​定义为他的事情办完的那个时刻。定义所有人的总不满意度S=Σni=1Di。

办事处工作人员会合理安排办事顺序,使得总不满意度最小,记为Smin​。

现在,很急的鸡来办事了,鸡可以在任意时刻要求工作人员放下手头的事情,立刻来处理鸡的事情,鸡的事情需要tc​时间处理完成。假设鸡插队后其余n人的总不满意度最小值变为Sc,若Sc−Smin≤M,则工作人员将允许鸡的插队,否则工作人员将拒绝。M是工作人员的容忍限度。

现在,请你回答Q组询问,即当工作人员的容忍限度为M时,鸡最早能在哪个时刻办完事。

输入描述:

第一行输入三个正整数n,Q,tc(1≤n,Q≤105,1≤tc≤109),含义如题面所述。

第二行输入nnn个正整数ti(1≤ti≤106),表示第i个人办事所需时间。

接下来QQQ行,每行一个正整数M(0≤M≤1018),表示该组询问中工作人员的容忍度。

输出描述:

对于每组询问的容忍度,求出鸡在该容忍度下最早能在哪个时刻办完事。

输入

4 3 6
4 3 2 1
0
14
1000000000000

输出

16
9
6

2.题解

  • 我们可以知道打断一个人和插入在他前面的效果是一样的,所以我们可以直接只考虑插入在他前面即可
  • 需要先排序(为了降低时间复杂度,建议使用快排)
  • 现在是给定了最大不满意度M,所以我们可以求出排在鸡后面的人数最多是M-tc,即可得到在鸡前面的人有哪些,将其时间累加并加上tc即可得到答案
#include<bits/stdc++.h>

using namespace std;

//快速排序
void quick_sort(int *q,int l,int r)
{
	if(l>=r) return;
	int x=q[l+r>>1],i=l-1,j=r+1;
	while(i<j)
	{
		do i++;while(q[i]<x);
		do j--;while(q[j]>x);
		if(i<j) swap(q[i],q[j]);
	}
	
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}

int main()
{
    int n,q,t;
    cin >> n >> q >> t;
    int a[n];
    for(int i=0;i<n;i++) cin >> a[i];
    quick_sort(a,0,n-1);
    while(q--)
    {
        long s;
        cin >> s;
        long x= s/t;
        long y=n-x;
        long ans=0;
        for(long i=0;i<y;i++) ans = ans+a[i];
        ans = ans +t;
        cout << ans <<endl;
    }
}

D.数组成鸡

1.题目描述

小鸡有一个由整数组成的数组,小鸡可以对这个数组进行任意次(可以不进行)全数组每个数加一或全数组每个数减一的操作。

现在,小鸡想让你回答Q次询问,每次询问给出一个整数M,你需要回答任意次(可以不操作)操作后是否可以使得给定数组的乘积等于给出的整数M。

输入描述:

第一行输入两个正整数n,Q(2≤n≤105,1≤Q≤5×105),表示数组长度与询问次数。

第二行输入n个空格分隔的整数ai(−109≤ai≤109),表示数组中的元素。

接下来Q个空格分隔的整数M(−109≤M≤109),表示询问的数字。

输出描述:

对于每个M,请你输出一行"Yes"或"No"(不含引号)表示是否可以令全数组的乘积等于给定M。

示例1

输入

4 9
1 -1 3 5
-75 19305 123 1 0 15 -15 1919810 114514

输出

No
Yes
No
No
Yes
No
Yes
No
No

题解

  • 注意到一个盲点:绝对值非1的最多30个(230>109超出了数据范围)
  • n> 30:枚举哪个数变成1或-1(使得绝对值非1的最多30个才可,所以只用考虑枚举每个数变成1或者-1的情况,其他情况绝对值非1必然大于30个),然后把此时数组乘积算出来,在枚举前先判断一下这 个数变的话能不能保证变后绝对值非1的最多30个,所以真正要枚举的数不多
  • n ≤ 30:从前往后从后往前各遍历一遍,防止漏情况(因为遍历范围是自己取的,从前往后从后往前遍历的范围不一致)
#include<bits/stdc++.h>
#define int long long
using namespace std;

void solve()
{
	int n, m; cin >> n >> m;
	vector<int>a(n); //存入各数
	map<int,int>cnt; //计入各数的个数
	set<int>ans; //存答案
	ans.insert(0);

	for(int i = 0; i < n; i ++)
    {
		cin >> a[i];
		cnt[a[i]] += 1;
	}
    
    //n<30
	if(n >= 30)
    {
		//一定要减少绝对值不等于1的数字个数。
		for(auto [x, y]: cnt)
        {
			if(n - cnt[x] - cnt[x - 2] > 30) continue;
            
            //该数变成1的情况
			int mul = 1;
			bool flag = true;
			for(int i = 0; i < n; i ++)
            {
				mul = mul * (a[i] - (x - 1));
				if(abs(mul) > 1e9)
                {
					flag = false;
					break;
				}
			}
			if(flag) ans.insert(mul);

            //该数变成-1的情况
			mul = 1, flag = true;
			for(int i = 0; i < n; i ++)
            {
				mul = mul * (a[i] - (x + 1));
				if(abs(mul) > 1e9)
                {
					flag = false;
					break;
				}
			}
			if(flag) ans.insert(mul);
		}
	}
    
    //n<=30
    else
    {
		//从前往后遍历
		sort(a.begin(), a.end());
		int tmp = a[0];
		for(int i = 0; i < n; i ++)
        {
			a[i] -= tmp;
		}
		for(int i = -1e6; i <= 1e6; i ++)
        {
			int mul = 1;
			bool flag = true;
			for(int j = 0; j < n; j ++)
            {
				mul = mul * (a[j] + i);
				if(abs(mul) > 1e9)
                {
					flag = false;
					break;
				}
			}
			if(flag) ans.insert(mul);
		}

		//从后往前遍历
		reverse(a.begin(), a.end());
		tmp = a[0];
		for(int i = 0; i < n; i ++)
        {
			a[i] -= tmp;
		}
		for(int i = -1e6; i <= 1e6; i ++)
        {
			int mul = 1;
			bool flag = true;
			for(int j = 0; j < n; j ++)
            {
				mul = mul * (a[j] + i);
				if(abs(mul) > 1e9)
                {
					flag = false;
					break;
				}
			}
			if(flag) ans.insert(mul);
		}
	}

	while(m--)
    {
		int x; cin >> x;
		if(ans.count(x))
        {
			cout << "Yes" << endl;
		}
        else
        {
			cout << "No" << endl;
		}
	}

}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	while(t--)
	solve();
}

E.本题又主要考察了贪心

1.题目描述

斗鸡联赛一共有n只鸡参加,第iii只鸡截至目前已经积了ai​分。

接下来还有m场比赛要进行,第iii场比赛的对阵双方是编号为ui和vi的鸡。积分规则是:胜方加三分,败方不得分,若战平则双方各得一分。

请你计算在最好的情况下,我们的一号选手(炸鸡)能够排到第几名。

注意若有多鸡并列,则排名取并列的排名,且不影响随后的排名(例如两只鸡并列第二名,则都视为第二名,排名其后的下一只鸡视为第四名)。

输入描述:

输入第一行包括一个整数T(1≤T≤100),样例组数。

对于每组样例:

第一行输入两个整数n,m(2≤n≤10,1≤m≤10),含义如题面所述。

第二行输入n个整数ai(0≤ai≤100),表示第i只鸡当前已经有的积分。

接下来的m行,每行有两个正整数ui,vi(1≤ui,vi≤n,ui≠vi),表示第i场比赛的对阵双方。

输出描述:

对每组样例,输出一个整数表示一号选手最好的情况下能够排到第几名。

输入

3
4 3
2 4 5 8
1 2
1 4
2 4
3 1
3 1 1
2 3
6 6
1 2 3 4 5 6
2 3
2 3
3 4
4 5
5 6
6 1

输出

1
1
4

2.题解

  • DFS:递归+回溯(因为观察一下,数据很小)
  • 不要被题目的贪心蒙骗
#include <bits/stdc++.h>

using namespace std;

const int N=2e6+10;

struct node
{
    int x,y;
}str[N];

int n,m;
int ans;
int dx[3]={0,1,3};
int dy[3]={3,1,0};
int a[N];

//一个dfs的递归
void dfs(int u)
{
    if (u>=m)
    {
        int cnt=1;
        for (int i=2;i<=n;i++) 
        {
            if (a[i]>a[1]) cnt++;
        }
        ans=min(ans,cnt);
        return ;
    }
    
    //dfs每一种情况 
    for (int i=0;i<3;i++)
    {
        int x=str[u].x,y=str[u].y;
        a[x] +=dx[i];
        a[y] +=dy[i];
        dfs(u+1);
        a[x] -=dx[i];//退层要还原 
        a[y] -=dy[i];
    }
}

void solve()
{
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=0;i<m;i++)
    {
        int x,y;
        cin>>x>>y;
        str[i]={x,y};
    }
    ans=n;
    dfs(0);
    cout<<ans<<endl;
}

signed main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T;
    cin>>T;
    while (T--) solve(); 
    return 0;
}

F.鸡数题

1.题目描述

鸡想问有多少个长为m的数组a同时满足以下条件:

1、对于任意的i,ai>0;

2、对于任意的整数1≤i≤m−1,ai<ai+1;

3、a1∣a2∣…∣am−1∣am=2n−1(之中 | 为按位或操作);

4、对于任意的i≠j,满足ai&aj=0(之中&为按位与操作)。

你的答案需要对109+7取模。

输入描述:

输入两个正整数n,m(1≤n,m≤105),含义如题所述。

输出描述:

输出一个答案,表示满足条件的数组个数,你的答案需要对109+7取模。

输入

4 3

输出

6

输入

1000 400

输出

376905797

2.题解

  • 转化问题为:将n个不同的小球装入m个相同的箱子中,问有多少种不同的情况,即第二类斯特林数,公式:{n,m}=(1/m!)*(k从0累加到m)(-1)^k * C(m,k)*(m-k)^n
  • 第一点说明了盒子里要么没有小球有么有小球(可多个)
  • 第二点说明了盒子的相同,因为最后都会按照升序排列,所以相当于盒子是相同的
  • 第四点说明了小球是不同的,所以ai&aj=0,相当于每个小球是二进制上的一个不同的位为1的数,其他位都是0
  • 第三点说明了有n个小球
  • 本题的思想就是将位运算的问题转化为斯特林数实例问题
#include<bits/stdc++.h>
#define ll long long

using namespace std;

const int mod=1e9+7;
const int N=1e5+10;
ll fact[N],infact[N];

//快速幂求a^k mod m
ll qmi(ll a, ll k, ll m)
{
    a%=m;
    ll res = 1 ;
    while (k)
   {
        if (k&1) res = res * a % m; //&1判断是否是奇数
        a = a * a % m;
        k >>= 1; //>>1是除以2
   }
    return res;
}

//预处理阶乘取模的余数和阶乘取模余数的逆元(费马小定理求逆元)
void init(int n=1e5)
{
    fact[0] = infact[0] = 1;
    for (int i = 1; i <= n; i ++ )
   {
        fact[i] = fact[i - 1] * i % mod;
        infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod;
   }
}

//计算组合数
ll C(int a,int b)
{
    return fact[a] * infact[a-b] %mod * infact[b] %mod;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    init();
    int n,m;
    cin >> n >> m;
    ll ans=0;
    for(int i=0;i<=m;i++) ans=(ans+(i%2?-1:1)*C(m,i)*qmi(m-i,n,mod)%mod+mod)%mod; //+mod为了保证大于0
    cout << ans*qmi(fact[m],mod-2,mod)%mod << endl;
    return 0;
}

G.why买外卖

1.题目描述

鸡很饿,鸡要吃外卖,今天点份炸鸡外卖!

鸡使用的外卖程序有若干个满减优惠,第i个优惠可以表示为"满ai元减bi元",多个满减优惠可以叠加。

满减的具体结算流程是:假设鸡购买的食物原价共为x元,则所有满足x≥ai​的满减优惠都可以一起同时被使用,优惠后价格记为y,则鸡只要支付y元就可以了(若y≤0则不需要支付)。

现在,鸡的手机里一共只有m元钱,鸡想知道,他所购买的食物原价x最多为多少。

输入描述:

输入第一行包括一个整数T(1≤T≤104)样例组数。

对于每组样例,第一行输入两个整数n,m(1≤n≤105,1≤m≤109),含义如题面所述。接下来的n行,每行输入两个正整数ai,bi(1≤ai,bi≤109),表示一个满减优惠。

保证所有样例的Σn≤105

输出描述:

对每组用例,输出一个整数,表示鸡能购买的食物原价x最多为多少。

输入

4
1 10
100 80
2 10
30 10
100 90
3 10
100 30
100 30
100 30
2 10
21 10
1000 1

输出

10
110
100
10

2.题解

  • 易得公式:原价=m+优惠价格
  • 用满减的需求金额划分区间,判断每个区间是否成立即可,可以从大往小遍历,一旦成功直接break,降低时间复杂度
#include<bits/stdc++.h>

using namespace std;

const int N = 100010;

struct Range
{
    int x,y;
    //按照满多少元的大小按照升序排序
    bool operator < (const Range &W) const
    {
        return x < W.x;
    }
}range[N];

int main()
{
    int s;
    cin >> s;
    while(s--)
    {
        int n,m;
        cin >> n >> m;
        for(int i=0;i < n; i++ )
        {
            int x,y;
            cin >> x >> y;
            range[i] = {x,y};
        }
        
        //按照满多少元的大小按照升序排序
        sort(range,range + n);
        
        long yuan = 0;
        long jian = 0;
        
        for(int i=0;i<n;i++) jian = jian + range[i].y;
        
        for(int i=n-1;i>=0;i--)
        {
            if(i!=n-1&&range[i+1].x-1-jian>=m&&range[i].x-jian<=m)
            {
                yuan = m + jian;
                break;
            }
            if(i==n-1&&range[i].x-jian<=m)
            {
                yuan = m + jian;
                break;
            }
            jian = jian - range[i].y;
        }
        
        if(yuan == 0) cout << m <<endl;
        else cout << yuan <<endl;
    }
}

H.01背包,但是bit

1.问题描述

共有n件物品,每件物品有价值vi与重量wi两个属性。但特别地,所选物品的总重量并不是每件物品的重量和,而是所有所选物品的重量进行按位或运算的结果。

请你计算,在所选物品总重量不超过m的情况下,所选物品的最大价值之和是多少(价值之和正常定义为所选物品价值的加和)。

输入描述:

第一行包括一个正整数T(1≤T≤104),表示用例组数。

每组用例的第一行包括两个整数n,m(1≤n≤105,0≤m≤108),含义如题面所述。接下来的n行,每行包括两个整数vi,wi(0≤vi≤108,0≤wi≤108),含义如题面所述。

保证所有用例的Σn≤105

输出描述:

对每组样例,输出一个正整数,表示所选物品的最大价值之和。

输入

3
4 11
1 8
1 4
1 5
1 1
4 11
5 8
1 4
1 5
1 1
4 0
2 0
0 0
3 0
4 1

输出

3
6
5

2.题解

  • 记所选物品重量或起来是c,枚举m里是1的某个bit,强制c里该位为0,则该位将m分成了前 后两部分
  • 对于前面的那部分位(更高位),要求所选的物品这些位必须是m的子集(即m对应位是1 才能选)
  • 对于后面的那部分位(更低位),没有任何限制
  • 二进制中如何才能达到这样的效果呢???我们可以每次-1即可,比如m二进制1001000减去1即为1000111,使得物品重量和1000111相与,结果依然是物品重量即可,也就是最后一位1的高位部分为必须为m子集,最后一位1的低位部分由于全是1,相与必然是重量该部分原本的值,则可以为任意,由此遍历
  • 那么如何遍历呢???每次去掉去后一位1(m&-1),再进行操作(-1)即可
#include<bits/stdc++.h>

using namespace std;

long long v[100005],w[100005];
long long n,m;

long long get(int x)
{
	long long sum = 0;
	for(int i = 1;i <= n;i++)
	{
		if((x & w[i]) == w[i]) sum += v[i];//位与小的数等于它本身才行 
	}
	return sum;
}

void solve()
{

	cin >> n >> m;
	long long ans = 0;
	for(int i = 1;i <= n;i++) cin >> v[i] >> w[i];
	ans = get(m);//刚好相等的情况
    
    //i -= (i & -i)每次去掉最后一位1,遍历每种1是0的情况
    //get(i-1)就相当于不考虑低位了,高位子集,低位无所谓
	for(int i = m; i ;i -= (i & -i)) ans = max(ans,get(i-1));
    
	cout << ans << endl;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int T;
	cin >> T;
	while(T--)
	{
		solve();
	}
	return 0;
}

I .It’s bertrand paradox. Again!

1.题解

fried-chicken为学生们布置了一项作业:

“随机生成105个平面上的圆,使得这些圆满足:圆心坐标为整点、圆半径为整数。所有圆上的每一个点都在−100≤x≤100和−100≤y≤100所确定的平面区域内(可以在边界上),允许有重复的圆,但要求生成的圆是随机的。”

但由于作业要求中"随机生成"这一点说的含糊不清,fried-chicken的两个学生,bit-noob和buaa-noob采用了以下两种不同的生成方法。

bit-noob的方法:

1、随机等概率地从开区间(−100,100)生成两个整数x,y。

2、随机等概率地从闭区间[1,100]中生成一个r。

3、判断(x,y)为圆心、r为半径的圆是否满足要求,若不满足,返回步骤2重新生成r,若满足,则将该圆加入到结果中。

buaa-noob的方法:

1、随机等概率地从开区间(−100,100)生成两个整数x,y,随机等概率地从闭区间[1,100]中生成一个r。

2、判断(x,y)为圆心、r为半径的圆是否满足要求,若不满足,返回步骤1重新生成x,y,r,若满足,则将该圆加入到结果中。

于是,两人使用各自的方法,各自生成了105个圆,将作业交了上去。

现在,给出一份作业,请你判断这份作业是哪位同学的。本题共有 50 组数据。

输入描述:

输入第一行是一个整数n(n=105),表示生成的圆的数量。
接下来第i行是三个整数xi,yi,ri(−100<xi,yi<100,0<ri≤100),表示这位同学生成的第i个圆。

输出描述:

输出"bit-noob"或"buaa-noob"(不含引号)表示这份作业来自哪位同学。

输入

10
1 55 1
-83 48 1
-70 -86 11
-74 12 18
39 46 18
1 78 11
78 -12 3
-86 -93 7
-56 8 26
-26 -90 4

输出

bit-noob

说明

该样例只为说明输入输出格式,实际的所有测试数据中都会严格按照输入描述中的要求、并不存在 n 为 10 的情况。

2.题解

  • 我们需要注意到bit-noob的方法生成坐标是均匀的,buaa-noob是不均匀的,那么我们可以取一个区域,看落点概率,并且设立一个阈值即可
#include<bits/stdc++.h>

using namespace std;

int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int n;
    cin >> n;
    
    //bit-noob方法的均匀落点概率(理想情况)
    double biaozhun = 141*141*1.0/(201*201);
    
    //计算真正的落点概率
    int sum=0,su=0;
    while(n--)
    {
        int x,y,r;
        cin >> x >> y >> r;
        sum++;
        if(x<=70&&x>=-70&&y>=-70&&y<=70) su++;
    }
    double ans=su*1.0/sum;
    
    //相差不超过0.2是我们设计的阈值
    if(fabs(ans-biaozhun)<0.2) cout << "bit-noob" << endl;
    else cout << "buaa-noob" << endl;
}

J.又鸟之亦心

1.题目描述

小又和小鸟是生活在一维世界的一对恋人,在一维世界中,每个位置坐标可以通过一个整数表示,两个位置的距离定义为两个坐标之差的绝对值。

小又和小鸟在同一家公司的两个分部工作,小又所在分部位于位置x,小鸟所在分部位于位置y。

接下来的一年中,公司依次有n个外派任务(按时间顺序给出),第i个外派任务要求一个员工外派调到位于ai位置的分部工作直至另行通知为止。作为公司唯二的两个空闲员工,小又和小鸟必须有一人前去该分部工作。

现在,请你帮忙决定每个任务是要外派小又还是小鸟,怎样才能让这对恋人相距尽可能近。具体来说,请求出在最优安排下,两人全过程中距离的最大值最小为多少。

输入描述:

第一行是三个整数n,x,y(1≤n≤105,0≤x,y≤109),含义如题所述。

第二行输入n个整数,第i个整数表示第i个外派任务的位置ai(0≤ai≤109)。

特别地,保证x,y,ai​之间不存在相同的两个数字。

输出描述:

输出一个整数,即两人全过程中距离的最大值最小为多少。

输入

2 0 10
5 6

输出

10

输入

1 4 5
2

输出

2

输入

3 2 1
3 4 5

输出

1

2.题解

  • 找一定的范围,用二分不断逼近答案
  • 每次思路是,固定一个点,找另一个点,依次轮回,看是否能遍历完成
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
#define endl '\n'

void solve(){
	int n, x, y;
	cin >> n >> x >> y;
	vector<int> a(n);
	for (int i = 0; i < n; i++) 
    {
		cin >> a[i];
	}

    /*auto check = [&](int d) 是一个lambda表达式。
    它接受一个整数参数d,并且使用捕获列表[&]来捕获当前作用域中的所有变量(包括引用)。
    这个lambda表达式可以用于定义一个函数对象,可以在需要的地方调用它。
    当调用check(d)时,它会执行lambda表达式中的代码。*/
    //不断轮回x,y不动,反复遍历看是否可以遍历完所有点,就是结果非空
	auto check = [&](int d) 
    {
		int lst = y;//lst相当于每一轮不动的点
		set<int> S;
		if (abs(x - y) <= d) S.insert(x);
		for (auto x : a) 
        {
			if (!S.empty() && abs(x - lst) <= d) S.insert(lst);
            //S.begin()和S.rbegin()都是迭代器,需要加*才是值
			while (!S.empty() && *S.begin() < x - d) S.erase(*S.begin());
			while (!S.empty() && *S.rbegin() > x + d) S.erase(*S.rbegin());
			lst = x;
		}
		return !S.empty();
	};

    //用二分法不断趋近答案
	int l = 0, r = 1e9;
	while (l < r) 
    {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << endl;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
    solve();
	return 0;	
}

K.牛镇公务员考试

1.题目描述

在刚刚结束的miaohunt 2023中,炸鸡块君独自一人花了两个多小时、写了200行代码、写出了《喵镇公务员考试》一题,炸鸡块君也不禁疑惑,怎么我找了四个acmer组队,还是要自己写代码?

好心的炸鸡块君既想让大家也体会到这题的痛苦,又不想把大家折磨到寒假营退款,于是炸鸡块君对原有的规则进行了简化:

一份有n道单选题的试卷,每道题可以用一个整数ai和一个长为5的字符串si表示,之中字符串仅含有大写的ABCDE字母、且每个字母出现恰好一次(即si是ABCDE的一个排列),si,j表示第si的第j个字符。

则第i道选择题的题面为:

第i题:第ai题的答案是()。

A. si,1​
B. si,2
C. si,3
D. si,4
E. si,5

现在,请你回答,正确完成全部n道题的答案有多少种。答案请对998244353取模。

输入描述:

输入第一行包括一个整数n(1≤n≤105),题目数量。

接下来每一行包括一个整数ai(1≤ai≤n)和一个字符串si,含义如题面所述。保证si是大写字母ABCDE的一个排列。

输出描述:

输出一个整数,表示正确完成全部n道题的答案有多少种,答案请对998244353取模。

输入

3
1 ABCDE
2 AECDB
3 BCDEA

输出

0

输入

5
1 AEDCB
5 ACBDE
4 BCDEA
5 EDCBA
3 CEBDA

输出

1

输入

2
2 ABCDE
2 EDCBA

输出

1

题解

  • 理解题意:这题的意思是第i题的答案通过si的对应可以找到第ai题的答案,因此每个i其实都指向一个ai,构成了基环内向树森林
  • 首先所有连到环的链可以直接无视,它们不影响答案,这是因为,这个链所连接的环上 那个点确定答案后、链上每个点的答案可以由此反向传播依次得到(至于为何每个点答 案唯一?这是因为该点走到环的路径上所有排列复合得到的仍是排列),因此,环上点 确定方案、则链也随之确定唯一一种方案,所以可以忽略链
  • 对于环,我们可以通过假设环入点的值,遍历一遍ABCDE,绕环一圈再看入点是否满足开始的假设,满足则是一种正确的情况
#include <bits/stdc++.h>
#define ll long long
#define pb push_back

using namespace std;

const int N=3e5+7;
const int p=998244353;
int tt; //样例数
int n; //题目数
int a[N],top=0; //表示i指向ai
bool b[N]; //是否遍历过,相当于一个标志
char s[N][7]; //存每个字符串

void solve()
{
	scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        scanf("%s",s[i]+1);
    }
    
    ll ans=1;
    for(int i=1;i<=n;i++)
    {
        int now=i;
        vector<int> v;//存入遍历的情况
        while(b[now]==0)//保证没有遍历过
        {
            b[now]=1;
            v.pb(now);
            now=a[now]; 
        }//遍历后now将指向一个基环内向树的入点
        
        auto it=find(v.begin(),v.end(),now);
        //假设值进行遍历,绕一圈判断入点的假设是否正确
        int sum=0;
        for(int j=1;j<=5;j++)
        {
            char tmp=j+'A'-1;
            char ii;
            for(auto to:v)
            {
                if(to==now)ii=tmp; //入循环时,ii存入了环第一个数的答案
                tmp=s[to][tmp-'A'+1];
            }//循环结束temp刚好指向环第一个数的答案
            if(ii==tmp)sum++;
        }
        ans=(ans*sum)%p;
    }
    printf("%lld\n",ans);
}

signed main()
{
	tt=1;
	while(tt--)
    {
		solve();
	}
	return 0;
}

L.要有光

1.题目描述

神说要有光,于是就有了本题。

如图所示,一个漆黑的世界可以用如图的三维坐标系来表示,XoY平面表示该世界的地面。

坐标系由以下几个组成部分:

1、一面无限宽无限高的白墙S,可以用如下公式表示:

x=-c&&z>=0

2、一面宽为2w高为h的绿墙W(厚度忽略不计),可以用如下公式表示:

x=0&&-w<=y<=w&&0<=z<=h

3、这个世界唯一的点光源L(体积忽略不计),点光源必须在图中的黑色线段上,黑色线段可以用如下公式表示:

x=c&&y=0&&0<=z<=d

神的目的是照亮这片大地,但同时也要防止人类给点阳光就灿烂。因此,神可以决定放置点光源L的位置(神必须放置点光源),使得未被照亮的土地面积尽可能大,请你输出未被照亮的土地面积的最大值

说明:对于地面上的一点,若其与点光源L的连线接触到了绿墙W,则该点未被照亮。

注意:神只想最大化未被照亮的土地面积最大值,墙面S上未被照亮的面积不算做土地面积。且我们不计算墙S背后(x轴负方向)的未被照亮的土地面积。

输入描述:

输入包含T(1≤T≤104)组测试用例。

每组测试用例包含四个整数c,d,h,w(1≤c,d,h,w≤104),含义如题面所述。

输出描述:

输出一个浮点数,表示你的答案。当你的答案的绝对误差或相对误差小于10−4时,答案被视为正确。

输入

1
2 4 2 1

输出

6

2.题解

  • 画画图,用几何知识可知,答案即为3cw
#include<bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int c,d,h,w;
        float ans;
        cin >> c >> d >> h >> w;
        ans=3.0*w*c;
        cout << ans<<endl;
    }
}

M.牛客老粉才知道的秘密

1.题目描述

现在,在本次比赛的主页点击"排名",您就会看到本场比赛的榜单,可以看到,榜单中直接列出了本场比赛的所有题目。

现在,作为牛客老粉,炸鸡想用这道题给大家科普一下牛客以前榜单的愚蠢之处:

牛客以前的榜单并不是现在这样,而是至多同时只显示六道题目。同时榜单上还有"向左"按钮与"向右"按钮来切换显示的题目。以"向右"按钮为例,点击一次该按钮会显示接下来的六道题,特别的,如果接下来的六道题超出了总题数,则会将最后一题放到当前显示的最右侧。"向左"按钮同理。

现在,你需要回答,对于n道题的一场比赛,显示的六道题目中最左侧的题目一共有几种可能取值。

以下面共n=14道题的情况为例:

初始时,显示了 A 到 F;点击一次"向右",显示了 G 到 L;再点击一次"向右",此时由于剩余题数不足六题,显示的六道题是 I 到 N;此时不能继续点击"向右",点击一次"向左",显示的六道题是 C 到 H;再点击一次"向左",由于剩余题数不足六题,显示的六道题是 A 到 F。

上述过程中,显示的六道题中,最左侧的题目编号分别是 A、G、I、C、A,因此答案为 4。

输入描述:

输入的第一行包括一个正整数T(1≤T≤105),表示测试用例的组数。

每组测试用例输入一个正整数n(6≤n≤109),表示本场比赛的总题目数。

输出描述:

对每组测试用例,输出一个整数,表示显示的六道题目中最左侧的题目一共有几种可能取值。

输入

2
14
6

输出

4
1

2.题解

  • n只用考虑是或不是6的倍数两种情况即可
#include<bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        int m;
        cin >> m;
        int sum=0;
        sum = m/6;
        if(m%6!=0) sum = sum*2;
        cout << sum <<endl;
    }
}
  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值