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; }
对于题解给出的做法 我也农夫山泉一下
将每个左括号看成+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是为了避免两边会出现空集的情况 这个题目我们就做完了