2024.4.5|牛客小白月赛90
A.小A的文化节
B.小A的游戏
C.小A的数字
D.小A的线段(easy version)
E.小A的任务
F.小A的线段(hard version)
心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。
小A的文化节
题目:
小A的学校举办了一年一度的文化节!
在文化节中有 n 个项目,其中参加第 i 个项目的欢乐度是 ai 。虽然小A很想把全部项目都体验一遍,但是她的时间是有限的,因此她只能参加其中的 m 个项目。
现在小A告诉你她参加了哪些项目,请你帮她计算一下她的欢乐度吧。
输入描述:
第一行两个正整数 n 和 m(1≤m≤n≤100) ,分别表示文化节总的项目数和小A参加的项目数。 第二行 n 个正整数,其中第 i 个数字
(1≤ai≤105 ) 表示参加第 i 个项目得到的欢乐度。 第三行 m 个正整数,其中第 i 个数字 (1≤b i≤n)
表示小A参加了编号为 bi的项目。数据保证 bi各不相同。
输出描述:
输出一行一个整数表示小A的欢乐度。
示例1
输入
5 3
1 2 3 4 5
1 3 5
输出
9
注意:
A题是签到题。
实践代码:
void solve(){
int n,m;cin>>n>>m;
int sum=0;
vector<int> a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=0;i<m;i++){
int x;cin>>x;
sum+=a[x];
}
cout<<sum;
}
小A的游戏
题目:
小A和小B在玩一个游戏,每局游戏的结果可能有胜平负三种。游戏的胜者得到 3 分,败者不得分,若打平则双方都得 1 分。
现在他们进行了若干局游戏,比分记录着小A为 X 分,小B为 Y 分。由于持续的时间太长了,他们不确定记录的比分是否是正确的了,请你来判断一下此时的比分是否合法吧。
输入描述:
多组测试。
第一行一个正整数
T(1≤T≤103) ,表示测试数据组数。
接下来 T 行,每行两个整数 X 和 Y(0≤X,Y≤109) ,分别表示比分所记录的小A和小B的分数。
小红希望你判断她的输出是否符合要求,你能帮帮她吗?
输出描述:
对于每组测试,如果合法输出一行 “Yes” ,否则输出 “No”(均不包含引号)。
示例1
输入
3
1 3
3 3
4 15
输出
No
Yes
No
注意:
B题贪心。
实践代码:
void solve(){
int x,y;cin>>x>>y;
int ans=abs(x-y);
if(ans%3==0) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
小A的数字
题目:
小A给定一个数字 n ,请你帮她找出从低位对齐后任意一位均与 n 对应数位不同的最小正整数。
对于本题题面描述中的从低位对齐后任意一位均与 n 对应数位不同,你需要保证你所输出的答案的位数小于 n 的位数时,即使在添加前导零至与 n 的位数相同后,也不应有任意一位的数字两两相同。
输入描述:
多组测试。
第一行一个正整数 T(1≤T≤103) ,表示测试数据组数。 对于每组测试数据,一行一个不含前导零的整数
n(2≤n≤109) ,表示所给的数字。
输出描述:
对于每组测试,输出一行一个正整数表示答案。
示例1
输入
3
2
10
101
输出
1
1
10
注意:
C题,分为两种情况(n中包含0和不包含0的情况)和一种特殊情况(n只有一位)。如果包含0就找数字n从左向右第一次出现0的位数是在哪一位,然后将此位设置为1,(左边的默认为前导0,所以一定不会跟n在此位左边的任何一位有相同数字)然后从此位开始往右边位数找:遇1变0,遇除1以外其他数(0,2…9)变1;如果不包含0就只管个位数就行(其他位补充前导0即可);如果n只有个位数,则遇1变2(题目中要求最小正整数),遇除1以外其他数变1。本题用字符串处理比较方便。
举个栗子:
n:2103842
ans:10000
实践代码:
void solve(){
string s,t;cin>>s;
if(s.length()==1){
if(s[0]=='1') {cout<<2<<endl;return;}
cout<<1<<endl;return;
}
if(s.find("0")==string::npos){
if(s[s.length()-1]=='1') {cout<<2<<endl;return;}
cout<<1<<endl;return;
}
for(int i=0;i<s.length();i++){
if(s[i]=='0') t+="1";
else if(s[i]!='0') {t+="0";}
}
int pos;
for(int i=0;i<t.length();i++){
if(t[i]=='0') continue;
else {pos=i;break;}
}
for(int i=pos;i<t.length();i++) cout<<t[i];
cout<<endl;
}
小A的线段(easy version)
题目:
本题为 easy version ,与 hard version 的区别仅为 m 的数据范围。
在一个标有 1−n 的数轴上给定 m 条线段,第 i 个线段的左右端点分别为 sti,edi ,求有多少种线段的选择方案可以使得数轴上的每个整数点至少被覆盖两次。
定义两种选择方案不同当且仅当至少有一个线段在两种方案中的状态(选/不选)不同。
由于方案数可能很多,所以你需要输出满足条件的方案数对 998244353 取模的结果。
输入描述:
第一行两个正整数 n(2≤n≤105) 和 m(1≤m≤10) ,分别表示数轴长度和线段个数。 接下来 m 行,每行两个正整数,其中第
i 行的两个正整数 sti 和 edi (1≤sti<edi≤n) 分别表示第 i 条线段的起点和终点。
输出描述:
输出满足条件的方案数对 998244353 取模的结果。
示例1
输入
5 4
4 5
1 5
3 5
1 4
输出
3
注意:
D题是一个典型的dfs+差分。
实践代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f
#define PII pair<int,int>
const int N = 1e5+10;
const int mod = 1e9+7;
int n,m,ans=0,use[12],l[12],r[12],a[N];//方案数ans,记录是否使用该线段use,左端点l,右端点r,差分数组a
void dfs(int now){
if(now==m+1){//搜索到一条分枝的尽头
memset(a,0,sizeof(a));
for(int i=1;i<=m;i++){
if(use[i]) {a[l[i]]++;a[r[i]+1]--;}//如果选了第i个线段,就用差分数组将两个端点包含的区间+1
}
int f=1;//f用来检查是否存在不满足要求的情况
for(int i=1;i<=n;i++){
a[i]+=a[i-1];//求差分数组的前缀和
if(a[i]<2) f=0;//不满足数轴上每个整数点至少被覆盖2次的要求,将f置为0
}
if(!f) return;
ans++;return;//都满足要求的,方案数ans+1
}
use[now]=1;//如果选now这条线段
dfs(now+1);
use[now]=0;//如果不选now这条线段
dfs(now+1);
}
void solve(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>l[i]>>r[i];
dfs(1);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout<<fixed<<setprecision(2);//保留两位小数
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
小A的任务
题目:
小A现在需要完成有序的 A 类任务和 B 类任务各 n 个,初始时只有第 1 个 A 类任务可以进行。进行第 i 个 A 类任务需在完成第 i − 1 个 A 类任务之后,进行第 i 个 B 类任务需要在完成第 i 个 A 类任务之后。且在同一时刻只能进行 A 类和 B 类中的一类任务,同一类的任务只能同时进行一个,任何一个任务都至多完成一次。
总共有 q 次询问,每次询问你需要回答完成 k 个 B 类任务至少需要多长时间。
输入描述:
第一行两个整数 n(1≤n≤105) 和 q(1≤q≤100) ,分别表示任务个数与询问次数。
第二行 n 个整数,其中第 i 个数字 (1≤ai ≤109 ) 表示完成第 i 个 A 类任务所需要的时间。第三行 n 个整数,其中第 i 个数字 (1≤bi≤109) 表示完成第 i 个 B 类任务所需要的时间。
接下来 q 行,每行一个整数 k(1≤k≤n) ,表示询问。
输出描述:
对于每次询问,输出一行一个整数,表示询问结果。
示例1
输入
4 3
1 2 3 4
4 1 2 3
1
2
3
输出
4
8
13
示例2
输入
5 2
19 1 20 2 17
12 20 17 4 2
3
5
输出
75
114
备注
对于样例一的第一个询问,需要先完成前 2 个 A 类任务,再完成第 2 个 B 类任务。
注意:
E题优先队列+枚举
实践代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f3f3f3f3f//long long 1e19
#define PII pair<int,int>
const int N = 1e5+10;
const int mod = 1e9+7;
vector<int> a(N),b(N);
void solve(){
int n,q;cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];//A类任务
for(int i=1;i<=n;i++) cin>>b[i];//B类任务
int k;
while(q--){
cin>>k;
priority_queue<int> qe;//大根堆(优先队列),存储当前b任务中最小的k个任务
int ans=INF,sum=0,qesum=0;//当前优先队列中k个元素的总和qesum
for(int i=1;i<=n;i++){
sum+=a[i];//a类任务时间直接加
qe.push(b[i]);qesum+=b[i];//将b任务的第i个存进优先队列
if(qe.size()>k) {qesum-=qe.top();qe.pop();}
//此时优先队列的元素个数大于k,那么qesum就减去当前优先队列中最大的元素(用较小的元素替换最大的元素),保证所用时间最少
if(qe.size()==k) ans=min(ans,sum+qesum);//记录做到当前k个b任务时的最优解(所用时间最少)
}
cout<<ans<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout<<fixed<<setprecision(2);//保留两位小数
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
小A的线段(hard version)
题目:
本题为 easy version ,与 hard version 的区别仅为 m 的数据范围。
在一个标有 1−n 的数轴上给定 m 条线段,第 i 个线段的左右端点分别为 sti,edi ,求有多少种线段的选择方案可以使得数轴上的每个整数点至少被覆盖两次。
定义两种选择方案不同当且仅当至少有一个线段在两种方案中的状态(选/不选)不同。
由于方案数可能很多,所以你需要输出满足条件的方案数对 998244353 取模的结果。
输入描述:
第一行两个正整数 n(2≤n≤105) 和 m(1≤m≤200) ,分别表示数轴长度和线段个数。 接下来 m 行,每行两个正整数,其中第
i 行的两个正整数 sti 和 edi (1≤sti<edi≤n) 分别表示第 i 条线段的起点和终点。
输出描述:
输出满足条件的方案数对 998244353 取模的结果。
示例1
输入
5 4
4 5
1 5
3 5
1 4
输出
3
注意:
F题跟D题的区别是m的范围,难度直接max。需要离散化+特判
实践代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f3f3f3f3f
#define PII pair<int,int>
const int N = 1e5+10;
const int mod = 998244353;
int dp[404][404][404];
void add(int &x,int y){//相加取模
x+=y;
if(x>mod)x-=mod;//直接传x的值回本身
}
void solve(){
int n,m;
cin>>n>>m;
vector<int>alls,s,t;
vector<PII>a(m+1);
for(int i=1;i<=m;i++){//将坐标点离散化
cin>>a[i].first>>a[i].second;
--a[i].first;//防止起点和终点重合误判,只要线段右端大于线段左端
alls.push_back(a[i].first);//将点压入点的集合
alls.push_back(a[i].second);
}
alls.push_back(0),alls.push_back(n);//还有0和n
sort(alls.begin(),alls.end());//进行排序形成数轴
alls.erase(unique(alls.begin(),alls.end()),alls.end());//删除重复的的点
//用到unique函数,来去重
auto get =[&](int x)->int{//定义一个函数参数为x,返回大于等于该数的值
return lower_bound(alls.begin(),alls.end(),x)-alls.begin();//返回坐标
};//不为普通函数,能在alls函数中找值
//用lower_bound应该是前面减了1吧
for(int i=1;i<=m;i++){
auto [s,t]=a[i];//将a[i]装入s,t数组
a[i]={get(s),get(t)};//再在alls中找相应的点的坐标
}
sort(a.begin(),a.end());//a数组值对应all点的下标
n=alls.size();//离散点总点数
dp[0][0][0]=1;//状态定义,dp[i][l][k]l表示0到l至少被覆盖两次,l到r表示被覆盖一次,r到n表示没有被覆盖
//当l为0,r为0时,表示初始默认0被覆盖两次,初始状态就有一种情况,此时有0个线段
for(int k=1;k<=m;k++){//表示m条线段
for(int i=0;i<=n;i++){//表示l
for(int j=i;j<n;j++){//表示r
add(dp[k][i][j],dp[k-1][i][j]);//先将前面的线段值赋给现在状态
if(a[k].first<=i){//当当前线段的左端点小于l时满足状态条件
add(dp[k][max(i,min(j,a[k].second))][max(j,a[k].second)],dp[k-1][i][j]);
//l值为线段右端点与k比较大小后判断哪个点更近,近的之前表示覆盖两次以上
//然后和标记覆盖两次以上的i进行大小比较更新大的那一个作为状态
//加上没有这个线段的时候
}
}
}
}
cout<<dp[m][n-1][n-1]<<"\n";
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout<<fixed<<setprecision(2);//保留两位小数
int t=1;
//cin>>t;
while(t--){
solve();
}
return 0;
}
心有猛虎,细嗅蔷薇。再见了朋友~