A、自动收小麦机(前缀和)
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
一、题目要求
在游戏MC(我的世界)中,如果小麦碰到水流就会掉落,但是mc中的水不能无限流动,在同一高度一桶水往一个方向流最多可以流动8格(算上本身一格)。
但是如果流动过程中遇到了阶梯下降了高度,则从下降的那一格开始重新计算距离,所以根据这个原理可以设计一种自动收小麦机,只需要从最高处倒一桶水就可以把所有小麦变成掉落物。
这次小小航也根据这个原理建造了一个自动收小麦机,但是小小航并没有精确的计算台阶的位置,当小小航建造完后发现机器不能一次收集所有小麦,现在已知小小航的收小麦机总长为 n 格 ,水流可以流 k 格,每一格上的小麦数量为ai。
共有q次询问(每次询问之间互不影响),每次询问一个整数x,在第x格放一桶水共可以收获多少小麦。(水只会从高的一端流向低的一端)
输入描述:
第一行三个整数n,q,k(1≤n,q,k≤10^5)
第二行 n个数表示每格上小麦的数量(0≤数量≤10^9)
第三行n个数表示每格的高度,保证非递减(0≤高度≤10^9)
接下来q行 每行一个数x,表示在第x格放一桶水(1≤x≤n)
输出描述:
q行,每行一个整数,表示能收获的小麦数量。
输入
4 1 2
1 1 4 5
2 2 2 3
4
输出
10
说明
在第4格放出水流后,水流会流向第3格,由于第3格高度比第4格低,所以水流继续向左流向第2格,因为平地水流只能流2格,所以到达第2格后水流停止,收获的小麦数量为1 + 4 + 5 = 10
示例2
输入
5 2 2
1 1 4 5 1
2 2 3 3 4
4
3
输出
9
6
二、思路:
注意:
1.流动过程中遇到了阶梯下降了高度,则从下降那一格开始计算距离
2.一桶水最多可以流向c格,包括自身那一格
解决方法:(准确找到水流最后流向的位置)
1.利用前缀和方便计算区间小麦的数量
2.对于连续的相同的高度,进行统计个数,设初始值k为1,看统计的个数是否超过c格。
若超过,则用当前下标减去c,即t=i-c,得出来数字则为水流最终流到的方格的位置;若没有超过则标记k为1,整个过程应该在原有的t的基础上进行累加(t的初始值为0)
三、代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+100;
int a[N],b[N],cnt[N];
int n,q,c,x;
void solve()
{
int i,j;
cin>>n>>q>>c;
for(i=1;i<=n;i++)
{
cin>>a[i];
a[i]+=a[i-1];//前缀和
}
int t=0,k=1;
b[0]=-1;//因为b[i]有可能为0
for(i=1;i<=n;i++)
{
cin>>b[i];
if(b[i]==b[i-1])
k++;//标记连续相同数字的个数
else
k=1;//标记
if(k>=c)
{
t=i-c;//由于c的限制,看其是否可以流c个方格
}
cnt[i]=t;//找到最终流向的位置
}
while(q--)
{
cin>>x;
int ans=a[x]-a[cnt[x]];//前缀和相减得出流过的小麦总数量
cout<<ans<<endl;
}
}
signed main()
{
int T=1;
while(T--)
{
solve();
}
}
/*
思路:
1.如何准确找到水流最后流到的方格位置
2.利用前缀和
样例:
a[i] 1 1 4 5
a[i] 1 2 5 11
b[i] 2 2 2 3
k 1 2 3 1
indx 1 2 3 4(indx下标)
t 0 0 1 1(t在原来数字的基础上更新)
cnt[i]0 0 1 1
a[x]-a[cnt[x]]
*/
C、悲伤的RT(窗口滑动)
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
一、题目要求
RT在表白后被拒,悲伤的RT决定去塞尔达·王国之泪里折磨人马发泄。为此,RT用马车拉了n个树枝(第i个树枝的攻击力为ai ,1≤i≤n, 1≤ai ≤1e9),准备用树枝与人马决斗。
善良的海利亚女神不想看到RT这样折磨人马和他自己,就用魔法给RT加了一个限制:他只能拿马车中前⌊n/c⌋个攻击力最小的树枝去和人马战斗(当c = 2, 树枝的攻击力为{1,2,3,4,5}时,RT可以拿走攻击力为1和2的树枝)。
但邪恶的加农多夫给了RT一个邪恶的魔法。RT可以把这n个树枝分成若干个连续的部分(假如a = {1,2,3,4},{{1,2},{3,4}}是一种合法的分割方式,但{{1,3},{2,4}}不是),装在不同的马车上,然后对这些马车施加魔法,这样RT可以从每个马车上取走该马车上前[k/c]个攻击力最小的树枝(k为马车上树枝的数量)。
现在请你帮RT算出他所能取出的树枝攻击力之和最大是多少?
输入描述:
第一行两个正整数n和c。树枝的总数量和海利亚女神对RT的限制。(1≤n,c≤1e6)
第二行n个正整数,n个树枝的攻击力 。( 1≤ai ≤1e9)
输出描述:
一个整数,RT所能取出的树枝攻击力之和最大值
示例1
输入
5 2
3 1 5 7 9
输出
8
说明
RT可以将前三个树枝放在一个马车上,第四和第五个树枝放在另一个马车上。从第一个马车只能取出攻击力为1的树枝,第二个马车只能取出攻击力为7的树枝。取出树枝的攻击力之和最大为8
二、思路
1.窗口滑动k个区间寻找每个区间内的最小值,将其存入预处理的数组(ps:如果预处理用两层for循环则会产生段错误,通过率为66.17%)
2.利用dp思想,先每一步都找出最优的策略,然后找当前窗口的最优的最大值,不断迭代答案,最后的结果一定是最优的
3.将样例作为解释(区分迭代与不迭代答案的区别):
思路:滑动窗口
5 2
a[i] 4 6 1 5 7
s[i] 4 1 5 7
ps:1没有迭代答案
dp[2]=s[2]+dp[0]=4
dp[3]=s[3]+dp[1]=1
dp[4]=s[4]+dp[2]=9
dp[5]=s[5]+dp[3]=8
asn1=dp[5]=8
ps:2迭代答案
5 2
a[i] 4 6 1 5 7
s[i] 4 1 5 7
dp[2]=s[2]+dp[0]=4
dp[2]=max(dp[1],dp[2])=4
dp[3]=s[3]+dp[1]=1
dp[3]=max(dp[2],dp[3])=4
dp[4]=s[4]+dp[2]=9
dp[4]=max(dp[3],dp[4])=9
dp[5]=s[5]+dp[3]=8
dp[5]=max(dp[4],dp[5])=9
ans2=dp[5]=9
显然ans1<ans2
*/
三、代码
#include<bits/stdc++.h>
#define int long long
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e6+100;
int a[N],p[N],s[N],dp[N];
int n,k;
void solve()
{
int i,j;
cin>>n>>k;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
//预处理s[i]数组,即在k的区间范围内找到最小值
int l=1,r=0;
p[1]=1;
for(i=1;i<=n;i++)
{
if(i-p[l]>=k)// 窗口不断滑动
l++;
while(l<=r&&a[p[r]]>=a[i])//看前一个是否比后一个大
{
r--;
}
p[++r]=i;//如果不满足if语句和while条件,则将其下标存入p
if(i>=k)
{
s[i]=a[p[l]];//将区间比较过之后的最小值,依次存入预处理的数组
}
}
//利用dp,若每一步都是最优,则最后结果也一定最优
int ans=0;
for(i=k;i<=n;i++)
{
dp[i]=s[i]+dp[i-k];//当前窗口最大值
dp[i]=max(dp[i],dp[i-1]);//找当前窗口最优的最大值,并往下不断迭代
}
cout<<dp[n]<<endl;
}
signed main()
{
int T=1;
while(T--)
{
solve();
}
return 0;
}
D、固执的RT(签到题)
链接:https://ac.nowcoder.com/acm/contest/61657/D
来源:牛客网
一、题目要求
RT你的帮助下取走了足够多的树枝,并用木制的马车拉着树枝去与人马决一死战。但他忘了一件事,人马是会喷火的。在战斗进行到一半时,卑鄙的人马用火焰将RT所有树枝烧光了。一般人可能已经放弃了,但固执的RT不愿意放弃。他决定再去收集树枝,和人马进行第二次战斗。
RT现在收集了n个树枝,第i个树枝的攻击力为ai。RT如果想打败人马,收集到的树枝攻击力之和至少为m。请你帮RT判断他现在收集到的树枝是否足够打败人马。
如果可以请输出“YES”, 否则输出“NO”(输出不带引号)。
输入
5 9
3 6 8 7 4
输出
YES
二、思路:
直接将a[i]进行累加,当其大于等于m时,输出YES,否则输出NO
三、代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
signed main()
{
cin>>n>>m;
int i,j;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
int ans=0;
int f=0;
for(i=1;i<=n;i++)
{
ans+=a[i];
if(ans>=m)
{
f=1;
break;
}
}
if(f==1)
printf("YES\n");
else
printf("NO\n");
return 0;
}
E、释怀的RT
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
一、题目要求
又一次收集完树枝后,RT做了防火工作,这次RT成功的使用了树枝战胜了人马。经过了长时间的痛苦折磨,RT释怀了并决定去海拉卢达陆上狩猎岩石巨人,用岩石巨人的心岩照亮心形湖来祭奠他还未开始就结束的爱情。
假设心形湖由1×n个方格构成,RT在每个方格上放了一个心岩,每个心岩有一个照亮范围x,代表着这块心岩可以照亮它左边x个方格和右边x个方格,但不能照亮心岩所在的方格(假如一个心岩在第5个方格,x=2,那么他只能照亮第3,4和第6,7个方格),现在请你求出心形湖有多少个方格被照亮。
输入描述:
第一行一个正整数n,心形湖格子的个数。( 1 ≤ n ≤ 1e6)
第二行n个整数,第i个数表示第i个心岩能照亮的范围。(0 ≤xi≤ 1e9)
输出描述:
一行,一个整数,表示照亮的格子数
示例1
输入
5
0 1 0 0 10
输出
4
说明
前四个方格被最后一个心岩照亮
示例2
输入
5 0 1 0 0 1
输出
3
说明
第一个方格和第三个方格被第二个格子的心岩照亮,第四个方格被第五个格子的心岩照亮,一共有三个格子被照亮
二、思路
1.若a[i]==0,则不考虑这个数字
2.当a[i]>=1时,则将其进行标记,找出它的区间范围(i-a[i],i+a[i]),它本身标记为0,
左边:int l=max(1,i-a[i]) 右边:int r=min(n,i+a[i]) 由于本身大于等于1,所以 a[i+1]=1
即 a[l]++ a[i]-- a[i+1]++ a[r+1]--
3.利用前缀和,寻找大于0的个数,大于0的有几个,就可以照亮几个
三、代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e6+10;
int a[N];
int n;
signed main()
{
cin>>n;
int i,j,x;
for(i=1;i<=n;i++)
{
cin>>x;
if(x==0)
continue;
int l=max(1ll,i-x);//1ll,防止数据类型过大存不下
int r=min(n,i+x);
a[l]++;
a[i]--;
a[i+1]++;
a[r+1]--;
}
int cnt=0;
for(i=1;i<=n;i++)
{
a[i]+=a[i-1];
if(a[i]>0)
{
cnt++;
}
}
cout<<cnt<<endl;
return 0;
}
G、爬山(二分)
链接:https://ac.nowcoder.com/acm/contest/61657/G
来源:牛客网
一、题目要求
暑假学校组织同学去景区爬山,但是当地的山峰分布十分奇特,所有的山峰都分布在一条东西走向的直线上(入口在东,出口在西),为了提高同学们的身体素质学校给同学们规定了一个“运动指标”,“运动指标“等于攀爬过程中上山高度与下山的高度的和。幸运的是景区给游客提供了缆车服务(缆车的行驶路线平行于地面),游客可以在景区入口选择任意一个高度乘坐缆车但是从此以后乘坐缆车的高度不能再发生改变,由于缆车不能穿过山体,所以当缆车的行驶高度低于当前山峰高度时缆车内游客需要在当前高度的东面山体下车并步行翻过山峰,当到达西面山体的缆车高度时游客需要继续乘坐缆车直到到达景区出口。例如景区三座山峰高度分别为100米、200米、150米,当选择缆车高度为120时,第一座山峰无需攀爬,第二座山峰需要攀爬80米,第三座山峰需要攀爬30米。
位置越高缆车上的风景越好,小明希望能看到最好风景但是同时需要满足学校要求的“运动指标”。
输入描述:
第一行两个正整数N、P,N表示景区山峰数量,P表示“运动指标”。
第二行N个正整数,表示每座山峰的高度。
(1<=N<=1e5,1<=P<=1e9,1<=山峰高度<=1e9)
输出描述:
一个非负整数表示你选择的缆车高度H,如果怎样安排都无法满足则输出-1。
示例1
输入
3 230 100 200 300
输出
192
示例2
输入
3 900 150 150 125
输出
-1
二、思路:
二分模板:
while(l+1<r)
{
int mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;//由于check里面的函数返回为假,则缩小右区间
}
1.主要补充check函数
2.由题意可知,位置越高缆车上的风景越好,小明希望能看到最好风景但是同时需要满足学校要求的“运动指标”。则check函数里面的主要功能是,如果mid<a[i],令h+=a[i]-mid;当h*2>=p时,则返回为真
三、代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int a[N];
const int inf=0x3f3f3f3f;
int n,p;
bool check(int mid)
{
int ans=0;
for(int i=1;i<=n;i++)
{
if(a[i]>mid)
{
ans+=a[i]-mid;
}
}
return ans*2>=p;
}
signed main()
{
cin>>n>>p;
int i;
for(i=1;i<=n;i++)
{
cin>>a[i];
}
int l=0,r=1e9;
if(!check(l))
cout<<"-1"<<'\n';
else
{
while(l+1<r)
{
int mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
}
cout<<l<<'\n';
}
return 0;
}
/*
思路:二分
while(l+1<r)
{
int mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;//由于check里面的函数返回为假,则缩小右区间
}
直到真正符合条件,才输出l
*/
I奶牛的寿命
一、题目要求
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
上帝养了一头奶牛,但是有一天奶牛偷吃了上帝草药,上帝十分生气于是奶牛必须要去地狱接受惩罚,上帝给了奶牛一个长达n的刑期但是同样允许奶牛对刑期进行最多操作log2(n)+1次,每次操作可以交换刑期的二进制形式下任意的两个位置上的数字,但是不能改变原来的二进制数的位数(不能有前导零),奶牛自然希望可以尽可能多的减刑,请问出奶牛最多可以减少多少刑期?
输入描述:
一个正整数n表示奶牛当前刑期。(n<2^31)
输出描述:
一个整数表示奶牛最多可以减少的刑期。
示例1
输入
14
输出
3
说明
只需要交换第1位和第3位,14(1110)-->11(1011),14-11=3。
二、思路
1.尽可能多的把0放在最高位的后面,且题目保证了,每次操作可以交换刑期的二进制形式下任意的两个位置上的数字,但是不能改变原来的二进制数的位数(不能有前导零),
2.先考虑两种特殊的情况,全是0,则直接输出0;
3.其他情况把新组成的二进制的和相加,再用原来的数减去新的数
三、代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
signed main()
{
cin>>n;
int k=0;
int sum=n;
int i,j;
while(n!=0)
{
a[k++]=n%2;
n=n/2;
}
int cnt=0;
for(i=k-1; i>=0; i--)
{
if(a[i]==0)
{
cnt++;
}
}
if(cnt==0)
{
cout<<"0"<<endl;
}
else
{
int p=k-cnt,sum1=0;
for(i=0; i<p-1; i++)
{
sum1+=pow(2,i);
}
sum1+=pow(2,k-1);
int ans=sum-sum1;
cout<<ans<<endl;
}
return 0;
}
k、卡特兰数
一、题目要求
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
卡特兰数是组合数学中一个常出现于各种计数问题中的数列。以中国蒙古族数学家明安图和比利时的数学家欧仁·查理·卡特兰的名字来命名,其前几项为(从第0项开始):1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452, ...
卡特兰数公式:h(n) = (h(n−1)∗(4n−2))/(n+1)= c[2n][n]-c[2n][n-1]
给定一个整数n,请你求出∏i=1nh(i)末尾有多少个连续的0。
输入描述:
一个整数n。(1 <= n <= 5e6)
输出描述:
一个整数,代表前n项卡特兰数之积末尾连续0的数量
示例1
输入
8
输出
2
说明
前8项卡特兰数之积为476150875200,末尾有两个连续的0
二、思路
由于要求有多少个0,所以可以找5的个数,5的个数和2的个数进行配对即可又因为是相乘,所以2的个数一定大于5的个数 ;
c[2n][n]-c[2n][n-1]=【2n!/((n!*n!))】-【2n!*n/((n-1)!*(n+1)!*n)】=【2n!/((n+1)!*n!)】
b=(2n+2)!/((n+1)!*(n+2)!) a=(2n!)/((n+1)!*n!)
b/a=2(n+1)/(n+2) 2n+2/n+2
2(n+2)/(n+1+2) 2n+4/n+3
每次遍历,a、b变化
b=b+2 a=a+1
b/a表示相邻两位的比值,这个比值是变化的
三、代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+7;
int n,m,p;
int a5[N];
int ck(int x)
{
int ans=0;
while(x%5==0)
{
ans++;
x=x/5;
}
return ans;
}
signed main()
{
a5[3]=1;
int a=4;
int b=5;
int i,j;
cin>>n;
if(n<=2)
{
cout<<"0"<<'\n';
}
else
{
int res=1;
for(i=4; i<=n; i++)
{
a++;//a每次都会加1
b=b+2;// b每次加2
a5[i]=a5[i-1]+ck(b)-ck(a);
//printf("a5[i]=%d\n",a5[i]);
res+=a5[i];
}
cout<<res<<'\n';
}
return 0;
}