2020腾讯笔试编程题

第一题很水,就不放了。
总体来说鹅厂的题较难,感觉考的很灵活,然后数据范围都很大,O(n^2)的做法肯定得超时,看了别人的代码以后恍然大悟觉得不难,代码量也很小,其实思维量很大,考试时候想不出来也很正常,继续努力,共勉!

第二题

在这里插入图片描述
示例一:
输入
2
1 1
2 2
输出
3

示例二:
输入
3
1 3
1 1
4 1
输出
6

分析

最重要的一点,先把不满意度的计算式整理一下,变成了 ( a i − b i ) ∗ j + b i ∗ n − a i (ai-bi)*j+bi*n-ai (aibi)j+binai ,显然我们可以先把 b i ∗ n − a i bi*n-ai binai放入不满意度总和中,然后以 a i − b i ai-bi aibi为条件进行排序,让 a i − b i ai-bi aibi值大的排在前面,这样乘以j就会得到更小的不满意度,最后遍历求和即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

int n;
struct node
{
    LL a,b;
}pep[100010];

bool cmp(node x,node y)
{
    return x.a-x.b > y.a-y.b ;
}

int main()
{
    cin>>n;
    LL ans = 0;
    for (int i=0 ; i<n ; i++){
        cin>>pep[i].a>>pep[i].b;
        ans += (pep[i].b*n - pep[i].a);
    }
    sort(pep,pep+n,cmp);
    for (int i=0 ; i<n ; i++)
        ans += (pep[i].a-pep[i].b)*(i+1);
    cout<<ans<<endl;
    return 0;
}

第三题

在这里插入图片描述
示例:
输入
4 100
3 4 5 4
输出
5

分析

题目描述较麻烦,m个工人全部在0点,货物分散在1至n的点上,应该怎么搬才能花的时间最短,显然这个时间存在一个范围,至少要花 n+1 天才能搬完(假如工人无穷多,只考虑第n个点能搬完即可),至多花 sum+n天搬完(假如只有一个工人则需要走完全部的点搬完全部的箱子),那么答案就在这个范围之间,用二分来计算,取左右范围的中间值m天,如果m天可以搬完,那就把范围缩小为[L,m-1],如果不能搬完范围变为[m+1,R],最后会出现L等于R的情况,即为答案。
难点在于如何判断m天是否可以搬完?这里需要注意,当天数固定的时候,对于点 i 来说,一个工人可以搬掉的东西为 m - i,如果当前的货物不足 m-i 件,这时候让一个工人来搬是一种浪费,我们把当前货物留着,和下一个点 i+1 的货物加在一起,如果大于 m-(i+1) 了,就派一个工人搬,这样这个工人会把前面剩余的货物都搬完并且可以搬掉一些i+1这个点的货物,也就是每一个工人在m天之内绝对不会闲着,这样处理之后我们只需要看工人数量够不够就可以了,假如在遍历点的过程中工人已经没了,那就是说天数不够,假如到达点n之后货物还有剩余(这个剩余量肯定是小于m-n的),这时候如果还有多出来的工人说明天数足够,没有工人了说明天数不够。
还有一个坑,假如点n处没有货物,那么至少要花的时间就变成了 n天,范围的下限L就出现了问题,所以我们要先判断一下有货物的最远的点在哪,把n设为这个点。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

//http://m.nowcoder.com/discuss/241764
//二分这个时间,下限是 n+1,上限是 n+全部箱子
//难点在于如何判断时间能否完成任务
//一个细节点:这里的n,假如第n个办公室箱子数为0,那下限就会减小为n

int n,m;
LL a[100010];
LL sum;

bool check(LL mi)
{
    sum = 0;
    int now = m;
    for (int i=1 ; i<=n ; i++){
        sum += a[i];
        while (sum >= mi-i){
            sum -= (mi-i);
            //一个人mi天可以搬走mi-i件物品
            now--;
            if (now < 0) return false;
        }
    }
    if (now > 0) return true;
    if (now==0)
        if (sum > 0) return false;
        else return true;
}

int main()
{
    cin>>n>>m;
    sum = 0;
    for (int i=1 ; i<=n ; i++){
        cin>>a[i];
        sum += a[i];
    }

    while (!a[n]) n--;
    LL l=n+1,r=n+sum;
    while (l <= r){
        LL mid = (l+r)>>1;
        if (check(mid)) r = mid-1;
        else l = mid+1;
    }
    cout<<l<<endl;
    return 0;
}

问题四

小Q在每一个期末的时候,都会对本学期学习的情况做一次全面的总结,如果这个学期有n天,那么小Q会对每一天的学习状态打一个分,他对一段时间学习状态的评分为这段时间学习状态最低的分数与这段时间学习状态分数之和的乘积,小Q想知道他在这个学期中学习状态评分最高的时间段评分为多少。
输入描述:
输入第一行包含一个数字n,代表本学期的天数,接下来的一行包含n个数字wi(1<=i<=n),代表每一天的学习状态分数。
1≤n≤100000;1≤wi≤100000;
输出描述:
输出一个数字代表小Q在这个学期中学习状态评分最高的时间段的评分。
示例:
输入
5
7 2 4 6 5
输出
60

分析

第一眼看感觉像区间动态规划,然后开了一个 dp[100001][100001]数组,dp[i][j]用来表示区间[i,j]之间的评分,编译器直接报错,内存限制,场面一度十分尴尬,当然暴力也可以得到一些分数,最后也是赛后看了评论区才知道,这个题可以很巧妙的模拟,首先遍历i从1到n,把第i天的学习状态当作是一个时间段的最小分数,然后往左往右去找这个时间段最远能取到哪(也就是说这个时间段中别的数都比w[i]大),把这个时间段评分记录下来不断比较得到最大值。
所以第一层循环是1到n,第二层循环是往左遍历找边界,往右遍历找边界,感觉足够应对大部分数据了,但是追求卓越的我们当然要继续优化,Let’s go!
使用一个arr数组来存当前 第i小的数字所在位置,例如,第一天的时候arr[1]=1表示当前最小的数字在1这个位置,假设第二天的数字更大,那么arr[2]=2,当前arr中有cnt=2个数,第三个数w[3]和w[arr[cnt]]比较,如果第三个数更小,那么cnt–,继续比较,直到cnt为0(则左边界为0)或者出现了w[arr[cnt]]比w[3]小(则左边界为arr[cnt]),并且w[3]是第cnt+1小的数,cnt++,arr[cnt]=3,这种做法维护了当前数左边的最小序列,当前数w[i]首先与w[i-1]进行比较,因为当前的arr[cnt]=i-1,若w[i]小则继续比较w[arr[cnt-1]],比较完成后w[i]会把这个序列尾部比它大的数全部取缔,下一个w[i+1]先与w[i]比较,这样找左边界的效率就得到了提高。
同理倒着遍历从n到1找右边界。

如果我们一开始就计算了sum[i],那求答案的时候就可以直接算 w [ i ] ∗ ( s u m [ r [ i ] ] − s u m [ l [ i ] ] ) w[i]*(sum[r[i]] - sum[l[i]]) w[i](sum[r[i]]sum[l[i]]),效率更高了。
一切算法的核心都是效率,也就是为了降低复杂度,有时为了降低时间复杂度,牺牲一些空间复杂度是值得的。

一年后的想法

时隔一年重新看这道题,其实就是个单调栈求左右边界,只是当时的我压根不知道什么单调栈,这里arr相当于在模拟单调栈的操作,既然看到了索性再写一下,请看第二份代码~

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long LL;

int n;
int w[100010],l[100010],r[100010],arr[100010];
//l[i]、r[i]表示比w[i]小的左右边界位置,用arr来优化,arr[i]表示第i小的数所在的位置
LL sum[100010];
int main()
{
    cin>>n;
    sum[0] = 0;
    for (int i=1 ; i<=n ; i++){
        cin>>w[i];
        sum[i] = sum[i-1] + w[i];
    }
    int now = 0;

    for (int i=1 ; i<=n ; i++)
    {
        while (now && w[arr[now]] >= w[i]){
            now--;
        }
        if (!now) l[i] = 0;
        else l[i] = arr[now];
        now++;
        arr[now] = i;
    }

    now = 0;
    for (int i=n ; i>0 ; i--)
    {
        while (now && w[arr[now]] >= w[i]){
            now--;
        }
        if (!now) r[i] = n;
        else r[i] = arr[now]-1;
        now++;
        arr[now] = i;
    }

    LL ans = 0;
    for (int i=1 ; i<=n ; i++)
        ans = max(ans,w[i]*(sum[r[i]] - sum[l[i]]));

    cout<<ans<<endl;
    return 0;
}

代码二

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
typedef long long LL;

int n, L[100010], R[100010];//L、R存放以w[i]为最小值的左右边界
LL w[100010], sum[100010];//sum存放前缀和
int main()
{
    cin >> n;
    sum[0] = 0;
    for (int i = 1; i <= n; i++){
        cin >> w[i];
        sum[i] = sum[i-1] + w[i];
    }
    stack<int> sta;
    while (!sta.empty()) sta.pop();
    sta.push(0);//先放一个0进去,这样不管top是啥都可以直接L=top+1
    for (int i = 1; i <= n; i++){
        while (sta.top()!=0 && w[sta.top()] > w[i]){
            sta.pop();
        }
        L[i] = sta.top()+1;
        sta.push(i);
    }
    while (!sta.empty()) sta.pop();
    sta.push(n+1);//同理先放一个n+1进去
    for (int i = n; i > 0; i--){
        while (sta.top()!=n+1 && w[sta.top()] >= w[i]){
            sta.pop();
        }
        R[i] = sta.top()-1;
        sta.push(i);
    }
    LL ans = 0;
    for (int i = 1; i <= n; i++){
            //cout << L[i] << " " << R[i] << endl;
        ans = max(ans, w[i]*(sum[R[i]] - sum[L[i]-1]));
    }
    cout << ans << endl;
    return 0;
}

第五题

在这里插入图片描述
答案对1e9+7取余。
示例:
输入
3 2
1 3
2 3
4 4
输出
6
5
5

分析

乍一眼看去,感觉是个排列组合问题,开搞开搞,然后只通过了50%,泪奔~~!
先解释一下题意,有两种花,红花随便摆,白花必须k盆一起摆,假如摆了a盆花,问有多少种摆花方案?那么a+1盆花有多少种方案?a+2呢?。。。摆b盆花有多少种方案呢?把摆 [a,b] 盆花的方案数求和输出一下,答案太大了就对1000000007取余。
其实是个dp,类比为完全背包,背包容量是摆花的盆数,物品有两种,其中红花的体积为1,白花的体积为k,求方案数。
那么递推式就是 d p [ i ] = d p [ i − 1 ] + d p [ i − k ] dp[i] = dp[i-1] + dp[i-k] dp[i]=dp[i1]+dp[ik],初始化当i<k时dp[i]=1,也就是只能摆红花,然后计算过程中注意一下取余即可。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define MOD 1000000007
typedef long long LL;
//完全背包,白花这个物品体积为k,红花体积为1,背包容量为[a,b]

int t,k;
LL dp[100010],sum[100010];
int main()
{
    cin>>t>>k;
    for (int i=0 ; i<k ; i++)
        dp[i] = 1;
    for (int i=k ; i<100010 ; i++)
        dp[i] = (dp[i-1] + dp[i-k])%MOD;
    sum[0] = 0;
    for (int i=1 ; i<100010 ; i++)
        sum[i] = (sum[i-1] + dp[i])%MOD;
    int a,b;
    while (t--){
        cin>>a>>b;
        cout<<sum[b]-sum[a-1]<<endl;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值