牛客算法竞赛入门课第三节习题
题目描述
多次查询[l,r]范围内的完全平方数个数
定义整数x为完全平方数当且仅当可以找到整数y使得y*y=x
输入描述:
第一行一个数n表示查询次数
之后n行每行两个数l,r
输出描述:
对于每个查询,输出一个数表示答案
备注:
n <= 100000
0<= l <= r <= 1000000000
题解
这是一题求完全平方数个数的题目。
关注到这题有多组数据,我们可以想到将所有完全平方数预处理,储存起来,询问时一一查询,此时该算法时间复杂度为,显然该时间复杂度过大,会导致超时。
可以发现,在区间中有且只有
个完全平方数,亦可知区间
中有且只有
个完全平方数,那么我们就可以得到区间
中有
个完全平方数,此时时间复杂度为
,满足我们的要求。当然,由于
有可能为
,而此时
为负值,因此我们要特别判断这种情况,此时的区间内有
个完全平方数。
int get(int x)
{
return x<0?0:sqrt(x)+1;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
int l,r;
cin>>l>>r;
cout<<get(r)-get(l-1)<<endl;
}
//system("pause");
}题目描述
贝西已经从公牛那里收到了N块巧克力(1<==N<=50,000),但她不想吃得太快,所以她想计划下一个D(1<=D<=50,000)天的巧克力进食时间表,以便在这几天里最大限度地提高她的最低幸福感水平。
贝茜的幸福水平是一个整数,从0开始,晚上睡觉时减半(如果必要的话)。然而,当她吃巧克力I时,她的幸福水平会增加整数H_i(1<=H_i<=1,000,000)。如果她一天吃巧克力,那一天的快乐就被认为是她吃巧克力后的幸福水平。贝茜坚持按她收到的顺序吃巧克力。
如果存在多个最优解,请打印其中任何一个。
输入格式
第1行:两个分隔的整数:n和D
第2行.N+1:第I+1行包含一个整数:
输出格式
第1行:一个整数,贝茜在接下来的D天的最低幸福感
第2行..N+1:第I+1行包含一个整数,即贝茜吃巧克力的日子
题解
题目要求的是最小值最大,因此可以直接二分答案然后判定。
对于一个答案,如果开心值不够就不断的吃,需要注意的是所有剩下的都要在最后一天吃完。
先二分求出最不开心那天的最大开心值ans,再对ans重新check一遍以记录正确日期
原因:因为这个程序会不管三七二十一地把所有的日期记录下来,不管可行与否,所以只有当求出最优解ans后,check记录的日期才会是正解。
const int N=50010;
int a[N];
int day[N];
int n,m;
bool check(LL mid,bool ok)
{
LL sum=0;
int k=0;
for(int i=1;i<=m;i++)
{
sum=sum>>1;
while(k<n && sum < mid)
{
if(ok) day[k]=i;
sum+=a[k++];
}
if(sum < mid) return false;
//cout<<"---"<<i<<' '<<sum<<endl;
}
if(ok)
for(int i=k;i<n;i++)
day[i]=m;
return true;
}
int main()
{
cin>>n>>m;
LL l=0,r=0;
for(int i=0;i<n;i++) cin>>a[i],r+=a[i];
while(l<r)
{
LL mid=l+r+1>>1;
if(check(mid,0)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
check(l,1);
for(int i=0;i<n;i++)
cout<<day[i]<<endl;
//system("pause");
}题意
歌曲由 个音符组成,第
个音符持续
拍。在时间 0 开始播放歌曲;
从时间 0 到时间 之前播放音符 1,从时间
到时间
之前播放音符 2,依此类推。
询问 个如下述形式的问题:在从时间
到时间
之前的间隔中,应该演奏哪个音符?
在时间 在
范围内时,演奏音符 1;
在时间 在
范围内时,演奏音符 2;
依此类推。
输入格式
第1行:两个空格分隔的整数:n和q
第2行.N+1:第I+1行包含单个整数:
第N+2.N+Q+1行:行N+I+1包含一个整数:
输出格式
第1行..Q:对于每个查询,打印一个整数,这是奶牛应该播放的音符的索引。
题解
解法一:使用upper_bound
const int N=50010;
int a[N];
int sum[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
while(m--)
{
int x;
cin>>x;
cout<<upper_bound(sum+1,sum+n+1,x)-sum<<endl;
}
//system("pause");
}解法二:使用 map 记录时间节点 对应的音符,
就可以使用 map::lower_bound() 函数得出对应的演奏音符。
const int N=50010;
int a[N];
map<int,int> mp;
int n,m;
int main()
{
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
mp[sum-1]=i;
}
while(m--)
{
int x;
cin>>x;
auto it=mp.lower_bound(x);
cout<<it->second<<endl;
}
//system("pause");
}题目描述
牛牛有件材料
和
件材料
,用
件材料
和
件材料
可以合成一件装备,用
件材料
和
件材料
也可以合成一件装备。牛牛想要最大化合成的装备的数量,于是牛牛找来了你帮忙。
输入描述:
输入包含组数据
第一行一个整数
接下来行每行两个整数
输出描述:
每组数据输出一行一个整数表示答案。
输入
复制5 4 8 7 6 8 10 100 4555 45465 24124
输出
复制2 2 3 50 13917
备注:
题解
解法一:(线性规划)
设以第一种方法合成装备m件,以第二种方法合成装备n件,则消耗2m+4n件材料a和3m+n件材料b。题目转化为求满足约束条件: 的m + n最大值。
运用高中所学简单线性规划知识,得到(m, n)所满足的区域,分三种情况
(1)
(2)
(3)
设z = m + n,再次运用线性规划知识,根据n = z - m求得z最大值,再分情况取整数即可
- 相交时,我们相当于要求出交点的m和n值(不过m和n要取整,所以要考虑向上或向下取整哪个大)。
- 所以我们就先求出该点的m值吧:此处因为求的是交点,所以可以直接将等式联立,就能得出:m = 1.0 * (4 * y - x) / 10。(此处m为浮点数)。
- 然后就要对m的上取整数和下取整数求出对应的n值。(这里要考虑对应的是哪条直线:我们可以看图分辨m变大或变小时,哪条曲线在边界内)
int main()
{
int T;
cin>>T;
while(T--)
{
LL x,y;
cin>>x>>y;
if(y<=x/4) cout<<y<<endl;
else if(x/2 <= y/3) cout<<x/2<<endl;
else
{
double px=(4*y-x)/10.0;
LL x1=floor(px), x2=ceil(px);
LL y1=(x-2*x1)/4, y2=y-3*x2;
cout<<max(x1+y1,x2+y2)<<endl;
}
}
//system("pause");
}解法二:(三分法)
对于本题,我们设制作了件装备一,那么装备二的个数就为
,我简单画了
和
函数的大致样式,不是很准确,可以想象出其大致趋势是一个单峰函数。下面我们用三分法求解即可。
LL x,y;
int check(int n)
{
return n+min((x-2*n)/4,y-3*n);
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>x>>y;
int l=0,r=min(x/2,y/3);
while(l<r)
{
int mid1=l+(r-l)/3;
int mid2=r-(r-l)/3;
if(check(mid1) > check(mid2))
r=mid2-1;
else
l=mid1+1;
}
cout<<check(l)<<endl;
}
//system("pause");
}题目描述
给出1~n的一个排列,统计该排列有多少个长度为奇数的连续子序列的中位数是b。中位数是指把所有元素从小到大排列后,位于中间的数。
输入描述:
第一行为两个正整数n和b ,第二行为1~n 的排列。
输出描述:
输出一个整数,即中位数为b的连续子序列个数。
输入
7 4
5 7 2 4 3 1 6
输出
4
题解
b如果要是中位数的话,大于他的数和小于他的数个数就需要一样,因为已经知道b,那么我们可以把大于b的数记为1,小于b的数记为-1,等于他的数记为0,那么我们现在求的就是区间和为0且里面有0的长度为奇数的区间个数(因为1和-1个数要一样,且还有一个0,所以长度为奇数这个条件在前两个条件里已经满足了)。
我们可以以0作为起点(只有一个0)分别看他的左边和右边,统计从他开始的左边后缀和为i的个数,和右他开始的边前缀和为i的个数,显然当左边后缀和为x的时候,右边的前缀和要为-x才能保证区间和为0,统计即可。
eg:化成0,1,-1之后的数列为:
-1 1 -1 -1 0 1 1 1
从0开始左边的后缀和为:
-2 -1 -2 -1
从0开始右边的前缀和为:
1 2 3
当前缀和为1的时候左边有两个-1,所以对答案贡献2,当前缀和为2的时候左边有两个-2,对答案也贡献2,当前缀和为3的时候左边没有-1,对答案没有贡献。
以上统计了左右配对的情况,还有从中位数位置起只向左边延伸和只向右边延伸的情况(左边某一位为0和右边某一位为0的情况),以及中间位为0的情况(此时只有一个数,区间长度为1,也为中位数)。
const int N=1e5+10;
int a[N],num[N];
int sum[N];
map<int,int> mp;
int n,b;
int main()
{
cin>>n>>b;
int pos=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i] > b) num[i]=1;
else if(a[i] < b) num[i]=-1;
else num[i]=0,pos=i;
}
for(int i=pos-1;i;i--)
{
sum[i]=sum[i+1]+num[i];
mp[sum[i]]++;
}
int ans=0;
for(int i=pos+1;i<=n;i++)
{
sum[i]=sum[i-1]+num[i];
ans+=mp[-sum[i]];//左右配对
}
for(int i=1;i<=n;i++)
if(!sum[i]) ans++;//单独为0
cout<<ans<<endl;
//system("pause");
}题目描述
输入描述:
第一行包含两个整数n, m,即牌的种数和joker的个数。第二行包含n个整数ci,即每种牌的张数。
输出描述:
输出仅一个整数,即最多组成的套牌数目。
输入
复制3 4 1 2 3
输出
复制3
说明
样例解释
输入数据表明:一共有1个1,2个2,3个3,4个joker。最多可以组成三副套牌:{1,J,3}, {J,2,3}, {J,2,3},joker还剩一个,其余牌全部用完。
备注:
数据范围
50%的数据满足:2 < = n < = 5, 0 < = m < = 10^ 6, 0< = ci < = 200
100%的数据满足:2< = n < = 50, 0 < = m, ci <= 500,000,000。
题解
直接贪心很难构造又因为是满足单调性的(套数大于答案的都不可行,小于等于答案的都可行)我们直接二分答案考虑验证。
如果当前待验证的数字的x(即判断当前手上的牌够不够组成x套)——首先,张数大于等于x的牌只需要每一套用一张就行,小于x的牌差几张就需要补几张joker,于是我们可以计算出究竟要补几张joker。如果最后需要补的joker数比手上有的joker数多,说明joker不够,不行。如果最后需要补的joker数比x还大,说明必然有两个joker在一套牌里,也不行。其他情况都是可以的。
const int N=55;
int a[N];
int n,m;
bool check(int mid)
{
int res=0;
for(int i=0;i<n;i++)
if(a[i] < mid)
{
res+=mid-a[i];
if(res > mid) return false;
}
return res<=m;
}
int main()
{
cin>>n>>m;
LL sum=m;
for(int i=0;i<n;i++) cin>>a[i],sum+=a[i];
int l=0,r=sum/n;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
//system("pause");
}
查看23道真题和解析