csu Problem D: Long Long

本文探讨了一道关于两个单调递增序列的算法问题,重点在于如何高效找出满足特定条件的组合数量。通过避免双重循环的方法,引入二分查找技巧,显著提升了算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem D: Long Long

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 207   Solved: 60
[ Submit][ Status][ Web Board]

Description

    现在有两个单调递增序列,第一个序列有N个整数,第二个序列有M个整数,现在你可以从第一个序列中选一个数x,然后从第二个序列中选一个数y,那么有多少种情况满足x+y<=K呢?

Input

    输入包含多组数据。
    对于每组测试数据,第一行包含两个整数NM , K (1 <= NM <= 105, 1 <= K <= 109),含义同上。接下来一行包含N个在[1, 109]范围内的整数,依次描述了第一个序列中的各个整数。再接下来一行包含M个在[1, 109]范围内的整数,依次描述了第二个序列中的各个整数。

Output

    对于每组数据,输出有多少种情况满足x + y <= K

Sample Input

2 3 5
3 7
3 5 7
4 5 6
1 2 3 4
1 2 3 4 5
4 5 9
1 2 3 4
1 2 3 4 5

Sample Output

0
14
20

HINT

    其实如果两个序列并不是有序的,我们也可以先通过排序使其变得有序,然后就和现在的这个题目一样了,不过由于这个题目并不是为了介绍排序,所以干脆就直接给出有序的序列了。

    由于int的范围有限,当要读入的数或者要输出的数超出了int的表示范围但没有超出long long的表示范围时,我们就要使用long long了。如果OJ的服务器是Linux的,那么用“%lld”(前面是两个小写的L)来控制long long的读入和输出,而如果OJ的服务器是Windows的,则需要用“%I64d”(前面是一个大写的i)来控制long long的读入和输出,这一点一般会在OJ的“F.A.Q”有所说明的。由于我们的服务器是采用的Linux操作系统,所以使用“%lld”就OK了。

    比如这个题目,极端情况就所有情况都满足,也就是N*M中情况,当N和M都取极限值时,显然结果就需要用long long来保存并输出了。

    那么这个题目具体要怎么做呢?

    也许你一眼就看出来了,两层for循环就可以搞定嘛,不过很可惜这样估计要妥妥超时,要不试一下?

    我们不妨仔细梳理一下思路,两层for循环做法的基本思想应当是首先枚举第一个序列中拿出的数x,然后再寻找第二个序列中有多少个整数y满足x+y<=K。其实我们不难发现,每次只要找到满足x+y<=K最大的y就可以了,不妨记为max_y,那么max_y以及max_y之前的数是一定都满足x+y<=K的,这样根据max_y所在的位置,我们就能直接计算出对于当前枚举到的x有多少个y满足x+y<=K了。而找这个max_y的过程真的需要一层for循环吗?你可能已经想到了,我们完全可以用“二分”的方法去找这个max_y,这样就能快很多了。如果你对“二分”还没有任何概念也没关系,因为我们还有另外一种办法也可以达到这个目的。

    我们不妨设先后枚举到的两个相邻的x分别为x1和x2,并设x1对应的max_y为max_y1,x2对应的max_y为max_y2,由于x1<x2,我们可以得到max_y2<=max_y1。但这个有什么用呢?当然有用了,我们在找max_y2的时候就可以直接从max_y1开始递减去找了。如果再枚举到x3呢?那么一样有max_y3<=max_y2,我们直接从max_y2开始递减去找max_y3。不过也许你可能会怀疑了:这样真的能比两层for循环快吗?好像这样也是两个变量不停地在循环呀?

    至于为什么会快还是交由你自己来思考吧,如果你对刚刚说的思路还没有什么概念的话不妨结合下面我给出的示例代码来看一下。



分析:

二分的简单题。给两个递增序列,固定其中一个序列,对另一个序列用二分即可。

ac代码:

#include <iostream>
#include<cstdio>
using namespace std;
const int maxn=100000+10;
int a[maxn],b[maxn];
int main()
{
    int n,m,k,i,j;
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        long long ans=0;
        for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
        for(i=1;i<=m;i++)
        scanf("%d",&b[i]);
        int y=m;
        for(i=1;i<=n;i++)
        {
            while(y&&a[i]+b[y]>k)y--;
            ans+=y;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值