jjzjj

「双指针&前缀和&回溯法」weight

孤星·璀璨 2023-03-28 原文

本题为3月14日23上半学期集训每日一题中B题的题解

题面

题目描述

已知原数列 \(a_1, a_2, \cdots, a_n\) 中的前1项,前2项,前3项,...,前n项的和,以及后1项,后2项,后3项,...,后n项的和,但是所有的数都被打乱了顺序。此外,我们还知道数列中的数存在于集合S中。试求原数列。

当存在多组可能的数列时,求字典序最小的数列。

输入

第1行,一个整数n。

第2行, \(2 \times n\) 个整数,注意:数据已被打乱。

第3行,一个整数m,表示S集合的大小。

第4行,m个整数,表示S集合中的元素。

输出

输出满足条件的最小数列。

样例输入

5
1 2 5 7 7 9 12 13 14 14
4 
1 2 4 5

样例输出

1 1 5 2 5

提示

数据范围:

$ n\leq 1000 ,m\leq 500,且S\in $ { \(1,2,⋯,500\) }

样例解释:

从左往右求和 从右往左求和
\(\phantom{0}1 = 1\) \(\phantom{0}5 = \phantom{1 + 1 + 5 + 2 + }5\)
\(\phantom{0}2 = 1 + 1\) \(\phantom{0}7 = \phantom{1 + 1 + 5 + }2 + 5\)
\(\phantom{0}7 = 1 + 1 + 5\) \(\phantom{}12 = \phantom{1 + 1 + }5 + 2 + 5\)
\(\phantom{0}9 = 1 + 1 + 5 + 2\) \(\phantom{}13 = \phantom{1 + }1 + 5 + 2 + 5\)
\(\phantom{}14 = 1 + 1 + 5 + 2 + 5\) \(\phantom{}14 = \phantom{}1 + 1 + 5 + 2 + 5\)

思路分析

本题题目tag指出这是一道可以用回溯法做的题,所以这里不考虑其他方法,有想法可以自己试一试.

这题有一个非常明显的暴力的思路,那就是我对每一个和是作为前缀还是后缀2种情况分别进行枚举,判断每种选择情况是否可行.这个思路可以使用DFS或者二进制枚举实现,但是显然这样的时间复杂度会高达 \(O(2 ^ N)\) ,属于无解算法,所以我们需要通过剪枝来减小平均时间复杂度.

那如何剪枝呢?上述暴力枚举的思路是先确定所有数据的前后缀情况,最后再判断的.那我们能否在确定前后缀情况的过程中直接判断出已经显然不成立的情况呢?是可以的.

首先,由于对前缀和进行差分即可得到每一个元素,即 \(a[i] = sum[i] - sum[i - 1]\) (此处题意中的前缀包含当前元素,所以是这样的形式),所以我们可以通过当前假设为前缀和的数减掉前面的那个前缀和,来计算出一个元素,如果这个元素不在给出的集合中,那就说明当前的情况是不成立的,那么接下来我们无论如何假设都不可能成立,所以此时我们可以无需继续进行接下来的假设,即进行剪枝.对后缀和同理.

其次,对于前缀与后缀,显然具有如下的性质: \(prefL[i] + prefR[i + 1] = sum = prefL[n - 1] = prefR[0]\) (其中 \(prefL\) 表示前缀和数组, \(prefR\) 表示后缀和数组),即一个数的前缀和加上其后面那个数的后缀和,会得到整个数组的总和,而第一个元素的后缀和以及最后一个元素的前缀和也正好等于整个数组的总和(因为它们的后缀/前缀就是整个数组).既然如此,当前确定一个数是一个位置上的前缀/后缀时,也就同时确定了与之对应的那个数为另一个位置上的后缀/前缀.所以我们实际上只需要处理所有和的一半即可.且显然prefL和prefR是一大一小的.所以我们可以对输入进来的各个和的序列进行一次排序,然后直接只处理小的那一半即可.且这样排序之后,由于集合中的数都是正数,所以距离最近的两个前缀和或两个后缀和一定是相邻的,故我们也无需单独去考虑当前这个前缀/后缀和的前面的那个前缀/后缀和它到底是哪一个了.

所以这题回溯法(所谓回溯法就是在DFS的同时进行剪枝)的思路也就很明确了,为了方便地在DFS的同时直接算出所求的数字,这里再利用双指针来辅助完成所求数组的生成:

  1. 先对输入进来的各个和从小到大排序
  2. 启动DFS,挨个对各个和进行假设,由于这里要求我们输出字典序最小的可能,所以越前面的数越小越好,又由于我们对各个和已经进行了排序,各个和优先假设为前缀和即可满足这一条件.故我们这里假设时先假设为前缀和,后假设为后缀和.
  3. 如果假设为前缀和时发现计算出来的当前数不能在集合中找到,那就证明这样的假设不行,就无需继续递归下去(直接剪枝),然后再尝试假设为后缀和,如果也不行,即当前无论如何假设都不能产生一个集合中的数,那就证明前面某一处的假设有误(产生原因是因为某个和在判定时发现既可以做前缀也可以做后缀,然后我们先尝试了前缀,但实际上其实应该是后缀),那也无需继续递归下去(直接剪枝).
  4. 在进行DFS的同时,维护两个指针,一个指向要生成数组中当前最左边尚为生成的元素的下标,另一个指向最右边尚为生成元素的下标.当我们假设一个和是前缀时,我们计算出的当前元素其实就是放在最左边尚为生成元素的指针所指向的位置上的,我们将它放入,然后指针移向下一个元素;假设为后缀的情况同理.这样通过这两个指针就可以在DFS的同时完成对所求数组的生成.
  5. 当那两个指针相离(就是左边那个大于右边)的时候,证明当前数组已经生成完毕,我们对各个和的假设已经完成.由于前面某个和在判定时可能会既可以做前缀也可以做后缀,我们先尝试了前缀,但其可能实际上应该是后缀,所以我们还需要再验证一下当前生成这个数组的总和对不对,根据前面的分析,这个总和显然是最大的那个和,所以我们验证一下是否相等即可,如果不相等,那就证明当前的假设不成立,继续尝试其他假设;反之,当前假设成立,且由于先尝试前缀,这一定是字典序最小的情况,所以这就是答案,我们即可停止所有的递归调用,输出结果.

如果上述文字单独看看不清楚的话可以和下面的代码对照阅读.

参考代码

时间复杂度: 最坏 \(O(2 ^ N)\),平均 \(O\left(\frac{NM}{max_{i=0}^{n \times 2 -1}(sum[i])}\right)\) (最坏的时间复杂度是假设没有发生剪枝的,在此题中难以达到;平均时间复杂度的计算方法涉及到概率期望,这里省略,感兴趣可以去问问newbing,因为你看到的这个式子就是newbing算的 (才不是因为我不会算期望) )

空间复杂度: \(O(N + M)\) (计入输入数据存放空间,其中递归本身也要使用 \(O(N)\) 的空间)

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

bool isOK = false;
int *sum;
int *res;
int n;
unordered_set<int> s;

// i为当前正在假设的和的下标,l为当前数组中最左边尚为生成的元素的下标,r为最右边尚为生成的元素的下标,prefL为当前左边的前缀和,prefR为当前右边的后缀和
void dfs(int i = 0, int l = 0, int r = n - 1, int prefL = 0, int prefR = 0) {
    // 基线条件
    if (l > r) { // 当前数组已生成完成
        // 验证当前数组的总和是否正确
        int a = 0;
        for (int i = 0; i < n; i++) {
            a += res[i];
        }

        if (a != sum[(n << 1) - 1]) { // sum[(n << 1) - 1]即sum[n * 2- 1],根据前面的分析,一定是所求数组的和,如果当前算出来的和不是它,那一定是个错误的解
            return;
        }
        isOK = true; // 已产生正确解,停止所有递归调用
    }

    // 已产生正确解,停止所有递归调用
    if (isOK) {
        return;
    }

    int a = sum[i] - prefL; // 当前和作为前缀和,所确定出来的数
    int b = sum[i] - prefR; // 当前和作为后缀和,所确定出来的数

    // 基线条件,如果当前和作为前缀和后缀都不可行,证明前面某处假设错误,且之后无论如何假设也是错误的,那么直接剪枝
    if (s.find(a) == s.end() && s.find(b) == s.end()) {
        return;
    }

    // 分别枚举作为前缀和后缀的情况,由于要按字典序最小的输出,所以先尝试作为前缀
    if (s.find(a) != s.end()) { // 如果当前确定出来的数在集合里,证明可能可以作为前缀
        res[l] = a; // 把当前算出来的数填入数组
        dfs(i + 1, l + 1, r, sum[i], prefR); // 继续假设下一个和(因为l已填,所以改为l + 1,同时更新前缀和)
        // 此处无需还原res,因为当前层的递归调用依然是在操作l位置上的数,会直接覆盖掉
    }

    // 已产生正确解,停止所有递归调用
    if (isOK) {
        return;
    }
    
    // 能执行这里说明前面假设成前缀和不成立,再次尝试假设为后缀和
    if (s.find(b) != s.end()) {
        res[r] = b; // 把当前算出来的数填入数组
        dfs(i + 1, l, r - 1, prefL, sum[i]); // 继续假设下一个和(因为r已填,所以改为r - 1,同时更新后缀和)
        // 此处无需还原res,因为当前层的递归调用依然是在操作r位置上的数,会直接覆盖掉
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n;
    sum = new int[n << 1]; // 各个和,n << 1等效n * 2,下同,注意优先级
    res = new int[n];

    // 输入各个和
    for (int i = 0; i < (n << 1); i++) {
        cin >> sum[i];
    }

    // 输入元素集合,为了方便查找,用unordered_set或set存放
    int m;
    cin >> m;
    while (m--) {
        int a;
        cin >> a;
        s.emplace(a);
    }

    // 对各个和排序
    sort(sum, sum + (n << 1));

    // 启动DFS
    dfs();

    // 输出生成的结果
    for (int i = 0; i < n; i++) {
        cout << res[i] << " ";
    }
    cout << "\n";

    delete[] sum;
    delete[] res;
    return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

有关「双指针&前缀和&回溯法」weight的更多相关文章

  1. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  4. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  5. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  8. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  9. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  10. ruby - 无法让 RSpec 工作—— 'require' : cannot load such file - 2

    我花了三天的时间用头撞墙,试图弄清楚为什么简单的“rake”不能通过我的规范文件。如果您遇到这种情况:任何文件夹路径中都不要有空格!。严重地。事实上,从现在开始,您命名的任何内容都没有空格。这是我的控制台输出:(在/Users/*****/Desktop/LearningRuby/learn_ruby)$rake/Users/*******/Desktop/LearningRuby/learn_ruby/00_hello/hello_spec.rb:116:in`require':cannotloadsuchfile--hello(LoadError) 最佳

随机推荐