二分贪心-D

  • 原题

    Description
    The SUM problem can be formulated as follows: given four lists A, B, C, D of integer values, compute how many quadruplet (a, b, c, d ) ∈ A x B x C x D are such that a + b + c + d = 0 . In the following, we assume that all lists have the same size n .
    Input
    The first line of the input file contains the size of the lists n (this value can be as large as 4000). We then have n lines containing four integer values (with absolute value as large as 2 28 ) that belong respectively to A, B, C and D .
    Output
    For each input file, your program has to write the number quadruplets whose sum is zero.
    Sample Input
    6
    -45 22 42 -16
    -41 -27 56 30
    -36 53 -37 77
    -36 30 -75 -46
    26 -38 -10 62
    -32 -54 -6 45
    Sample Output
    5

  • 思路&解析
    从每一列里各取一个数,求有多少组四个数的和加起来为零。
    先分别求出前两列后两列的所有的和,然后对后两列的和进行排序,对后两列进行二分查找看有多少种情况即可。

  • AC代码

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int a[4002][4],sum1[16000002],sum2[16000002];
int main()
{
    int n,mid;
    while(~scanf("%d",&n))
    {
        for(int i=0;i<n; i++)
        {
            scanf("%d%d%d%d",&a[i][0],&a[i][1], &a[i][2],&a[i][3]);
        }
        int k=0;
        int m=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
            {
                sum1[k++]=a[i][0]+a[j][1];
                sum2[m++]=a[i][2]+a[j][3];
            }
        sort(sum2,sum2+k);
        int cnt=0;
        for(int i=0;i<k;i++)
        {
            int left=0;
            int right=k-1;
            while(left<=right)
            {
                mid=(left+right)/2;
                if(sum1[i]+sum2[mid]==0)
                {
                    cnt++;
                    for(int j=mid+1;j<k;j++)
                    {
                        if(sum1[i]+sum2[j]!=0)
                           break;
                        else
                        cnt++;
                    }
                    for(int j=mid-1;j>=0;j--)
                    {
                        if(sum1[i]+sum2[j]!=0)
                           break;
                        else
                           cnt++;
                    }
                    break;
                }
                if(sum1[i]+sum2[mid]<0)
                    left=mid+1;
                else
                    right=mid-1;
            }
        }
        printf("%d\n",cnt);
    }
    return 0;
}
Nano-ESG数据资源库的构建基于2023年初至2024年秋季期间采集的逾84万条新闻文本,从中系统提炼出企业环境、社会及治理维度的信息。其构建流程首先依据特定术语在德语与英语新闻平台上检索,初步锁定与德国DAX 40成分股企业相关联的报道。随后借助嵌入技术对文本段落执行去重操作,以降低内容冗余。继而采用GLiNER这一跨语言零样本实体识别系统,排除与目标企业无关的文档。在此基础上,通过GPT-3.5与GPT-4o等大规模语言模型对文本进行双重筛选:一方面判定其与ESG议题的相关性,另一方面生成简明的内容概要。最终环节由GPT-4o模型完成,它对每篇文献进行ESG情感倾向(正面、中性或负面)的判定,并标注所涉及的ESG具体维度,从而形成具备时序特征的ESG情感与维度标注数据集。 该数据集适用于多类企业可持续性研究,例如ESG情感趋势分析、ESG维度细分类别研究,以及企业可持续性事件的时序演变追踪。研究者可利用数据集内提供的新闻摘要、情感标签与维度分类,深入考察企业在不同时期的环境、社会及治理表现。此外,借助Bertopic等主题建模方法,能够从数据中识别出与企业相关的核心ESG议题,并观察这些议题随时间的演进轨迹。该资源以其开放获取特性与连续的时间覆盖,为探究企业可持续性表现的动态变化提供了系统化的数据基础。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
<think>我们使用贪心策略:希望上升子序列尽可能长,那么就要让序列上升得尽可能慢,即每次在子序列末尾添加的元素尽可能小。 维护一个数组 `d`,其中 `d[i]` 表示所有长度为 `i` 的上升子序列中末尾元素的最小值。注意:`d` 数组是严格递增的(因为如果长度为 `i` 的上升子序列的最小末尾比长度为 `i+1` 的还大,那么长度为 `i+1` 的那个子序列的前 `i` 个元素构成的子序列的末尾就会小于 `d[i]`,与定义矛盾)。 算法步骤: 1. 初始化:令 `d[1] = nums[0]`,长度 `len = 1`。 2. 遍历数组 `nums`(从第二个元素开始): - 如果当前元素 `nums[i]` 大于 `d[len]`,则直接加入 `d` 数组末尾,并且 `len++`。 - 否则,在 `d[1..len]` 中二分查找第一个大于等于 `nums[i]` 的位置 `pos`,并更新 `d[pos] = nums[i]`(因为我们要让长度为 `pos` 的上升子序列的末尾元素尽可能小,所以用更小的 `nums[i]` 替换掉原来的 `d[pos]`)。 注意:这里二分查找的是第一个大于等于 `nums[i]` 的位置(因为我们希望替换掉第一个比 `nums[i]` 大的元素,这样替换后序列仍然有序,且我们让这个位置的末尾元素变小了,为后续提供更多可能)。 最终 `len` 就是最长上升子序列的长度。 注意:这个算法只能得到最长上升子序列的长度,如果要还原出具体的序列,需要另外记录路径(但这里我们只求长度)。 为什么正确? 我们维护的 `d` 数组并不一定是真实存在的子序列,但是它的长度就是最长上升子序列的长度。因为每次我们扩展长度时(即遇到比末尾大的元素),就相当于在原来的某个上升子序列后面添加了一个元素,使得长度加一。而当我们替换某个位置的值时,我们并没有改变当前的长度,但是让这个位置的末尾元素更小了,这样在后续的遍历中,我们就有可能构造出更长的子序列。 时间复杂度:O(n log n) 下面给出C++实现:</think>### LIS贪心算法与二分查找实现详解 贪心+二分查找是求解最长上升子序列(LIS)问题的最优解法,时间复杂度为 $O(n \log n)$,空间复杂度为 $O(n)$。其核心思想是**维护一个最小末尾元素序列**,使序列尽可能缓慢增长。 #### 算法核心思想 1. **贪心策略**:让上升子序列增长尽可能慢,即每次添加的元素尽可能小 2. **状态定义**:`d[i]` 表示长度为 `i` 的上升子序列的最小末尾元素 3. **关键性质**:`d` 数组严格递增(证明:若 $d[i] \geq d[i+1]$,则 $d[i+1]$ 对应的序列前 `i` 个元素构成的子序列末尾元素 $< d[i]$,矛盾) 4. **二分优化**:利用 `d` 的有序性,通过二分查找确定插入位置 #### 算法流程 1. 初始化 `d[1] = nums[0]`, `len = 1` 2. 遍历数组元素: - 若 `nums[i] > d[len]`:扩展序列,`d[++len] = nums[i]` - 否则:二分查找 `d[1..len]` 中第一个 $\geq$ `nums[i]` 的位置 `pos`,更新 `d[pos] = nums[i]` 3. 最终 `len` 即为 LIS 长度 #### C++ 实现代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; int n = nums.size(); vector<int> d(n + 1, 0); // d数组,d[0]不使用 int len = 1; d[1] = nums[0]; for (int i = 1; i < n; i++) { if (nums[i] > d[len]) { // 扩展序列 d[++len] = nums[i]; } else { // 二分查找插入位置 int left = 1, right = len; while (left <= right) { int mid = left + (right - left) / 2; if (d[mid] < nums[i]) { left = mid + 1; } else { right = mid - 1; } } // 更新d数组 d[left] = nums[i]; } } return len; } int main() { vector<int> nums = {10, 9, 2, 5, 3, 7, 101, 18}; cout << "最长上升子序列长度: " << lengthOfLIS(nums) << endl; // 输出: 4 return 0; } ``` #### 关键步骤解析 1. **初始化**:`d[1] = nums[0]`,初始长度为1 2. **遍历处理**: - 当前元素 `7`:大于 `d[3]=5`,扩展序列:`d[4]=7`,`len=4` - 当前元素 `3`:小于 `d[3]=5`,二分查找位置2(`d[2]=3`),更新 `d[2]=3` 3. **二分查找**:在有序数组 `d[1..len]` 中定位第一个 $\geq$ 当前元素的位置 #### 复杂度分析 - **时间复杂度**:$O(n \log n)$ - 遍历 $n$ 个元素,每次二分查找 $O(\log n)$ - **空间复杂度**:$O(n)$(存储 `d` 数组) #### 算法正确性证明 - **维护最小末尾**:每次更新确保 `d[i]` 是长度为 `i` 的 LIS 的最小可能末尾 - **单调性保证**:$d[1] < d[2] < \cdots < d[len]$ 严格成立 - **长度最优**:最终 `len` 对应真实 LIS 长度(可通过数学归纳法证明) #### 扩展应用 1. **输出具体序列**:额外记录路径数组 `path`,回溯时从 `d[len]` 开始反向追踪[^1] 2. **非严格递增**:修改比较条件为 `nums[i] >= d[len]` 和 `d[mid] <= nums[i]` 3. **二维LIS问题**:如俄罗斯套娃问题(先按宽度排序,高度求LIS)[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值