河南萌新联赛2024第(一)场:河南农业大学 题目讲解c++
链接:https://ac.nowcoder.com/acm/contest/86639/A
来源:牛客网
题目A描述
给定一个整数 n,你可以进行以下三种操作
操作1: +1
操作2; +2
操作3: ×2
问最少需要多少次操作可以将 0 转为为 n 。
我们可以根据逆向思维,反推从n到0需要的最少次数。
根据二进制数最后一位判别是-1还是/2
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int num=0;
//如果输入为0则直接输出0
if(n==0){
cout<<0;
return 0;
}
//如果输出部位1或2,则一直反复迭代应该是-1还是/2
while(!(n==2||n==1)){
if(n&1) num++,n--;
else {
num++;
n=n>>1;
}
}
//从1或2到0都只需要1个操作数
cout<<num+1;
}
链接:https://ac.nowcoder.com/acm/contest/86639/B
来源:牛客网
题目B描述
某天朵拉穿越到了一个冒险世界中,该世界共有 n 个冒险关卡,朵拉可以选择任意一个关卡作为起始关卡,若朵拉选择了 iii 号关卡,则完成该关卡的冒险后,朵拉可以选择回到原来的世界即冒险结束,或选择传送到第 ai号关卡,****(其中对于任意一个** i 号关卡,其 ai号关卡是固定的。)由于朵拉喜欢探险,因此她想向你请教她最多可以探险多少个不同的关卡。(其中,对于关卡i、ji、ji、j,当且仅当 i=ji=ji=j 时,我们才认为 i 和j 是同一个关卡。)
输入描述:
输入包含 2 行。
第一行一个正整数 n(1≤n≤105)n(1≤n≤10^5)n(1≤n≤105),代表关卡的数量。
第二行 n 个正整数,其中第 iii 个正整数 ai(1≤ai≤n)a_i(1≤a_i≤n)ai(1≤ai≤n) 代表完成第 iii 个关卡后会被传送到的关卡。(若 i≠ji≠ji=j,则可能存在 ai=aja_i=a_jai=aj)
输出描述:
输出包含一行一个整数,表示朵拉最多可以探险的关卡的数量。
我们可以通过常规的实现,从遍历所有的点,从每个点走,直到走不了位置。典型的dfs题目,不过考虑到时间限制,需要剪支,一个A点能够访问到的其他所有点,这些点的路径肯定没这个A点的长度长。由此我们建立dfs
#include<bits/stdc++.h>
using namespace std;
//当前所到点,所有点的下一个点,所有点被访问的情况,被剪枝的点
int dfs(int n,vector<int> &s,vector<int> &a,int num,vector<int> &ss){
if(a[n]==1)
{
return num;
}
a[n]=1;
ss[n]=1;
return dfs(s[n],s,a,num+1,ss);
}
int main(){
int n;
cin>>n;
vector<int> s;
s.push_back(0);
for(int i=0;i<n;i++){
int a;
cin>>a;
s.push_back(a);
}
int max=0;
vector<int> ss(n+1,0);
for(int i=1;i<=n;i++){
if(ss[i]) continue;
vector<int> b(n+1,0);
int n1 =dfs(i,s,b,0,ss);
if(max<n1) {
max=n1;
}
}
cout<<max;
}
链接:https://ac.nowcoder.com/acm/contest/86639/C
来源:牛客网
题目C描述
在某幼儿园中共有 nnn 个小朋友,该幼儿园的老师为这 nnn 个小朋友准备了 nnn 份不一样的零食大礼包。每个小朋友只能选择一个,但老师并不知道小朋友们喜欢什么类型的零食大礼包,因此,老师让小朋友们分别说出了他们喜欢的零食大礼包都有哪些,老师希望能根据小朋友们的叙述来让所有的小朋友们都能吃到他们喜欢的零食。若并非所有的小朋友都能吃到自己满意的零食,请问老师最少还应购买多少份零食大礼包来保证所有的小朋友都能吃到自己满意的零食。题目保证任意一个小朋友都会喜欢这 nnn 种大礼包中的至少一种。
输入描述:
输入包含 n+1n+1n+1 行。
第一行一个正整数 nnn (1≤n≤500)(1≤n≤500)(1≤n≤500),表示该幼儿园小朋友的数量。
接下来 nnn 行,每行先给出一个正整数 kik_iki(1≤ki≤n)(1≤k_i≤n)(1≤ki≤n),代表第 iii 个小朋友喜欢的零食大礼包的种类数量,然后给出 kik_iki 个正整数,第 jjj(1≤j≤ki)(1 \leq j \leq k_i)(1≤j≤ki) 个正整数 aj(1≤aj≤n)a_j(1≤a_j≤n)aj(1≤aj≤n) 代表第 iii 个小朋友喜欢的零食大礼包的编号。(保证每行的 kik_iki 个正整数不重复,且 ∑i=1nki≤2×105∑_{i=1}^{n} k_i≤2\times10^5∑i=1nki≤2×105 )
输出描述:
若所有的小朋友都能吃到自己喜欢的零食,则输出 “Yes”(不带双引号);
反之,则在第一行输出“No”(不带双引号),并在第二行输出老师还应购买的零食大礼包的最少的个数。
这题是二分图的最大匹配问题,使用匈牙利算法,即可快速完成求解。算法思路就是,先一个个尝试一对一匹配,发现匹配不了时,去观察之前匹配的是否还能匹配其他对象,如果能则继续匹配,不能则跳转下一个。
#include<bits/stdc++.h>
using namespace std;
// 记录小朋友能选择的零食
int s[501][501];
// 记录当前已经探索过的零食
int vis[501];
// 当前零食被哪个小朋友选择
int r[501];
int n;
bool match(int i){
//探索能访问且之前没有探索过的零食
for(int j=1;j<=n;j++){
if(s[i][j]&&!vis[j])
{
//记录拜访过的零食(防止递归死循环)
vis[j]=1;
//如果当前零食没有被选择,或者之前的小朋友还能选择其他的零食,则匹配成功
if(!r[j]||match(r[j])){
r[j] = i;
return true;
}
}
}
return false;
}
int main(){
//完成数据的录入
cin>>n;
int t;
for(int i=1;i<=n;i++){
cin>>t;
for(int j=0;j<t;j++){
int a;
cin>>a;
s[i][a]=1;
}
}
int ans=0;
//每一个小朋友做出选择
for(int i=1;i<=n;i++){
//消除之前探索的路径
memset(vis,0,sizeof(vis));
if(match(i)){
ans++;
}
}
if(ans==n){
cout<<"Yes";
}
else
{
cout<<"No"<<endl;
cout<<n-ans<<endl;
}
}
链接:https://ac.nowcoder.com/acm/contest/86639/D
来源:牛客网
题目D描述
小蓝有 ttt 组询问,每次给定两个数字 lll,rrr 你需要计算出区间 [l,r]\left [ l,r \right ][l,r] 中所有整数在二进制下1的个数之和。由于结果特别大,你只需要计算出结果模998244353之后的值即可。
输入描述:
第一行输入一个正整数 ttt 表示询问组数。
接下来每行两个整数 l,rl,rl,r 。
(1≤t≤105)\left ( 1\leq t\leq 10^{5} \right )(1≤t≤105)
(1≤l≤r≤1018)\left ( 1\leq l\leq r\leq 10^{18} \right )(1≤l≤r≤1018)
输出描述:
每行输出一个整数表示答案。
这题是关于数位dP的问题,不了解的可以去搜索学习一下思想,也就是说我们只需要求f(a-1),f(b)即可,
关于规划的思想是这样的,假设是二进制的101,也就是5,那么从高位往低位开始看,我们可以给最高位选择0或1,当为0时,后面的数字不受限制,也就是后两位可取任意两位数,那么它有2*(位数)个排列方式A:2*2=4,每个排列有(位数)个数B:2,由于1和0各占一半,所以A*B/2=4即为能直接求得的1的数量,也就是当最高位为0时,我们已经求出了后两位所有范围内1的个数。接下来时当我的最高位为1位的情况下,由于我们默认开始遍历时是有限制的条件,因为1满足限制条件那么我们这个最高位为1时,它的1的个数应该为,在限制条件下,后面范围的数量*1+f(次高位)。当n==0时,返回1的个数。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll lim_ =998244353; //限制条件
int b[64]; //将输入数据转换为二进制
ll e[64]; //每个二进制对应的权重
int maxp = 0; //记录转换二进制后,二进制的最高位索引
ll Get1Num(ll n,bool lim) {
ll ans=0;
if(n==0){
//当到了最低位时返回1的个数。
if(b[n]==1) return 1;
else return 0;
}
//从0遍历到当前数的当前位的数。
for(int i=0;i<=b[n];i++){
//如果在限制条件下,继续迭代遍历
if(i==b[n]&&lim){
//遍历下一位
ans= (ans+Get1Num(n-1,true)+lim_)%lim_;
//如果当前位为1,说明后面限制下能得到的数的个数都会和这个1组成,也就是说明会得到1*后面限制下能得到的数的个数个1
if(i==1){
ans++;
for(int j=n-1;j>=0;j--){
ans = (ans+e[j]*b[j]+lim_)%lim_;
}
}
}
//不在限制条件下,可直接求出后面位数能得到的1的个数
else
{
ans=(ans+e[n-1]*n+lim_)%lim_;
}
}
return ans;
}
void init(ll n){
if(n==0){
return;
}
int i=0;
while(n!=0){
b[i] =n&1;
e[i] = 1ll<<i;
n=n>>1;
i++;
}
maxp = i-1;
}
void Solve() {
ll a2,b2;
cin>>a2>>b2;
ll a1;
ll b1;
memset(b,0,sizeof(b));
memset(e,0,sizeof(e));
//将数转换为二进制
init(a2-1);
//获取从1-a1所有数的1的数量
a1 = Get1Num(maxp,true);
memset(b,0,sizeof(b));
memset(e,0,sizeof(e));
init(b2);
b1 = Get1Num(maxp,true);
cout<<(b1-a1+lim_)%lim_<<"\n";
}
int main(){
int t;
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
cin>>t;
while(t--) {
Solve();
}
}