9.24 模拟赛订正题解

昨天的题好难啊qwq T1 T2 写完 就剩一个小时了 看了T3 好几眼 还是不会写 然后搜索也没写 T3 成功爆零

T1 题目大意: 给定一个 仅有小括号组成的序列 判断有多少个合法的子串 合法的意思就是说 满足括号一一匹配

然后 思考半时 看到括号 让我着实想到 写栈 这种括号问题 一般都和栈有关系 只是口胡 所以发现

对于一个括号序列 要么他就是都是并列在一块的 那么答案非常好计算 那么我们考虑 括号里有括号 的情况

对于一个括号 如果他前面有一个合法括号 当前这个也是合法的 贡献就是1+1 那么我们考虑如果前面有num个连续合法括号 那么紧接着当前遇到一个合法的括号 那么我们 贡献变成了 num+1

所以当前我们遇到一个括号 我们要判断他前面是否有连续的括号 所以我们 不断累加 当前答案 复杂是(n)的  也就是我们不断执行 这样一个操作

对于栈为空 那么我们将 当前元素放进去 然后当 当前元素是:" ) " 并且当前元素是"( " 就代表我们已经 遇到一个合法括号了 那么我们将栈顶弹出来 

再判断一下 栈中是否为空 如果为空 我们的贡献+1 不为空 我们们将累加上 栈顶的pre  并且 将栈顶的pre累加 为什么 需要单独做 而不能一起呢 ?

其实我们是为了避免这种情况 前面一部分是个合法的 并且我们已经单独 处理完了 因为我们有执行 弹出栈的做法 但是紧接着遇到一个 右括号你是不能将栈顶元素累加的pre累加 因为你此时栈已经为空了

所以我们 对于栈为空 和不为空 单独讨论 然后 我们需要注意的是 每次 我们单独处理完一个括号 实际上已经处理完了 将代价不断累加到 栈顶的pre 直到当前者一堆的计算完 

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
char a[N];
struct node {
    int x,pre;
} b;
stack<node> s;
int main() {
    freopen("bracket.in","r",stdin);
    freopen("bracket.out","w",stdout);
    cin>>a+1;
    int len=strlen(a+1);
    ll ans=0,sum=0;
    for(int i=1;i<=len;i++) {
        if(a[i]=='(') b.x=1;
        else b.x=2;
        if(!s.empty()) {
            if(s.top().x==1&&b.x==2) {
                s.pop();
                if(s.empty()) {
                    ans+=(sum+1);
                    sum++;
                }
                else {
                    ans+=(s.top().pre+1);
                    s.top().pre++;
                }
            }
            else s.push(b);
        }
        else s.push(b);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

对于题解给出的做法 我也农夫山泉一下 

将每个左括号看成+1 每个右括号当作-1 然后求出前缀和数组s 当一段l,r合法的时候 $s[l]==s[r]$ 

并且 $s[l],s[l+1]....s[r-1]$ 之间没有比 $s[l]$ 更小的数字 这是合法的必要条件 思考一下 为什么 

考虑只有第一个限制条件 用指针i 从左到右扫描 并用f数组记录以下信息 $f[x]$ 表示截止到i之前 有多少 j 满足s[j]=x 当枚举到i时 只需要 把 f[s[i]] 加入答案

这题解过于神仙 鉴于我并不是这样做的 所以我们不用纠结这个题解了 

T2 写了个错误做法 至今不明白为什么 跑过了 而且还不慢 不bb了 害怕被hackqwq

其实我原来做过一道 很类似 但是数据范围较小的题目 题目的大致意思是 对于n个球 每个小球有一个权值

对于给定的X 考虑存在多少对$v_i,v_j ,$ 使得 $V_i+V_j \le X$ 问你有多大的概率 n,vi<=1e6 请你先思考 再往下看

这种快速数点的东西 我们不妨写个数据结构qwq  显然 开个1e6的树状数组 对于输入的$v_i$ 我们查询 $X-v_i$ 的前缀和 因为 你已经将当前点存在的点 作为1了

所以查询前缀和 就知道有多少个满足题意的 我们还要考虑 当前这个点 贡献两次的代价 减去即可

然后 不考虑顺序 我们显然可以 二分 复杂度nlogn 今天的模拟赛 也就是等下要总结的做法 也可以用单调指针的做法实现 

所以我们不妨来 讨论一下 T2 的正解 最近水的时间比较多 有必要学习一下新的优秀算法 

对于每一个di 我们可以 拆成 $d_i=a_i*b+r_i$ 对于 不同的b 我们根据$a_i$分组 &a_i&相同的时候 我们分到一组 

然后有点类似分块??? 但是块的大小是 我们需要 注意的地方 我们之所这样做是因为 我们的数据范围是1e9 考虑到这样的数组我们是开不下的 

一次性处理一个组内的数字 然后就是 控制b的大小 可以保证 我们存在当前一个数再一个组 另一个 数距离他不超过两组(自行证明

然后 我们一次处理一个数时 将其他两组 都加进 然后考虑 组内全部遍历一遍 所以我们的组是大小是(m/b) 然后b大概取到我们空间可以接受的范围内 (b=$\sqrt{m}$)

T3 昨天听了源神的做法 这个题目太神仙了 有必要脑子清醒再来就讲这个做法 先讲一下map的做法

fi j k 表示我们将前i个元素分成了 两组 一组&起来是 j 一组&起来是 k 目标就是fn m m m是可能的结果根据数据范围 枚举即可

因为我们考虑到一个一个事情是 &的情况 有0即为0 所以某一位变成  0 之后 有生之年没机会 变成 1了

所以我们记录的状态没多少 但是 考虑到这个状态 我们存不下 所以 我们考虑用一个map 存一下我们的状态 

然后就是考虑这种计数类的东西 我们最需要避免的就是 子问题的重复 所以我们这里 考虑容斥原理

但是这里我们需要用到n维的容斥 我觉得大家都会 所以 $$ANS=\sum_{i=0}^{131071} (-1)^c*f(c)$$

会容斥的应该都懂这个式子的原理  $f(c)$ 表示在c的限制下的 方案数 变量c显然枚举的是子集

所以我们现在要解决的问题就是 $f(c)$ 怎么计算 c具体就是指后几位的情况 1我们 留下考虑 0我们不管 然后考虑到一个事情是

当前有些位一定是 一边是0 一边是1 然后这些为1的我们考虑 把他们捆在一起 把他们合成一个数字 然后如果存在多个位有这样的限制 我们可以用并查集来做这个合并的做法

我们记录一下 合并后的元素个数num 那么这些位置我们是随便放的 那么 $2^{num}-2$ 减去2是为了避免两边会出现空集的情况 这个题目我们就做完了 

posted @ 2019-09-25 13:57  Tyouchie  阅读(39)  评论(0编辑  收藏  举报