Link
Solution
就是枚举有多少个1求有多少个含有这些个1的数字。。 然后一开始在Windows计算器上看1e15是几位数,结果可能是少看了几个0少算了几位然后数组开小了然后T飞了然后本地还一切正常 指数不会爆long long,可以直接不膜。如果一定要膜,需要膜。
Code
1 |
|
就是枚举有多少个1求有多少个含有这些个1的数字。。 然后一开始在Windows计算器上看1e15是几位数,结果可能是少看了几个0少算了几位然后数组开小了然后T飞了然后本地还一切正常 指数不会爆long long,可以直接不膜。如果一定要膜,需要膜φ(p)。
1 | #include "lucida" |
真的很想自己做出来,于是就想按套路先DP预处理再用输入去卡DP值。。 然而真的想不出来。 所以套路要有,但只会套路做题也是不行的。
数位DP可以边DP边卡上下界×2。 只要在状态里设计上需不需要继续卡边界即可×2
1 | #include "lucida" |
总算明白了大神们口中说的“剖完之后套用链上做法”的方法了。。。
要对链加标记算前缀和,要想办法转化成序列的问题。然而普通的DFS序可能会把路径弄成很多段,这不好。如果用树链剖分剖出的DFS序,一个路径在DFS序上最多O(logn)段,就好了。
1 | #include "lucida" |
一开始被题意唬了一下。。
对mulx操作,在左边插入一个0,删掉右端点并把系数加到右端点右边的端点 注意含有0,所以需要加加减减,然而Splay内部的空节点也要加加减减,所以要小心
1 | #include "lucida" |
数位DP模板?
然而有一点需要注意:长度不足n的必须单独算(或者我没想出好办法?)
想了很长时间想巧妙地避免分开处理,发现不好避免。
所以数位DP就可以类比AC自动机DP,可以把长度小于给定串的单独处理。
1 | #include "lucida" |
sro orz
预处理出块与块之间的答案,预处理出每个数字在前i块的出现次数 然后对于零散部分遍历,记录每个数字的出现次数,利用整块的某个数字的奇偶性判断对答案的贡献+1/-1。
似乎序列分块的大体套路就是处理块与块的答案和想办法快速地计算零碎部分对答案的贡献?
1 | #include "lucida" |
小时候写过树状数组套主席树的版本,时空O(nlog2n)。众所周知,这个空间复杂度是非常不友好的。 今天翻到jcvb巨神的博客,发现他给了一种非常interesting的做法:值域线段树套区间平衡树。研究一番,写完之后感觉很不错。
众所周知,序列上的主席树可以利用可持久化实现时空O(nlogn)的前缀和,从而解决区间K值问题。 放到树上,如果不带修改,那就可以直接用序列的可持久化线段树方法,从根节点DFS下去,在节点u的值域线段树的[l,r]区间,维护u到root路径上值在[l,r]的节点个数。这样,两点u,v的路径上,值在[l,r]的节点个数c(u,v),就是c(u)+c(v)−c(lca)−c(fa[lca])。套用序列的方法查询即可。 现在有了修改,显然不可能暴力重建主席树,因为一次修改会影响子树的所有的点。 等等,一次修改影响子树所有的点?不禁让人思考能否进行区间操作?然后就联想到了DFS序。没错,DFS序就是一个满足子树的所有点是一个区间的序列。 区间操作的两种办法:用兹瓷区间修改的数据结构打标记;或者差分,把区间修改变成单点修改。待修改的树上主席树是通过套一个树状数组,绝对化相对的方法来实现的。这样,线段树就不需要可持久化了,因为本来可持久化是要维护前缀和,但现在树状数组就维护了前缀和。一次修改,会修改O(logn)个线段树,每个线段树修改的时间是O(logn)。一个点的值至多将在O(logn)个线段树出现,粗略地估计总次数,绝对不会超过O(logn)。所以得到了一个时空O(nlog2n)的优秀做法。
1 | #include<cstdio> |
现在考虑优化的话,主要就是要考虑压缩空间,否则太不友好。注意到,空间之所以那么大,是因为把主席树建在了内层,一个点的权值可能出现在O(logn)棵线段树中,出现一次就至多带来O(logn)个节点,比较浪费。 那么考虑把值域线段树建在外层,那么这一部分的空间就是O(n)。考虑如何在内层维护整个树。 用DFS序列应该就不好弄了。。涉及到路径,DFS序似乎就无力了。。。 于是祭出大杀器:括号序! 一个树的括号序,指的是每个点在进栈的时候放一个左括号,出栈的时候放一个右括号。 然后把一棵树的括号序写下来,会发现一个惊天大秘密:从第一个括号开始,到pos的左括号,把匹配的括号删掉,恰好可以得到从根节点到pos的路径! 于是就可做了: 在值域线段树的每个节点[l,r]挂一个平衡树,平衡树维护所有值被[l,r]覆盖的点的左括号位置和右括号位置。左括号的权值为1,右括号为−1,这样做一下前缀和,就可以得到根节点到一个点的路径上有多少点的值在[l,r]之内了!然后就可以做了。 空间复杂度O(nlogn)。
1 | #include "lucida" |
被POI的智商题虐惨了,写几道数据结构颐养身心
就是普普通通的一个套了二分的点分治。 然而大部分人的做法都被hack了。 考虑一道最典型的点分治:楼教主男人八题Tree 我写的做法是在每层点分治,对每个子树双指针+归并。 极端情况:一个点,一个子树有2n个点,剩下的是2n−1个一个点的子树。而且,链上点的权值都比剩下的一堆点小。那么归并的总复杂度就是O(4n2)。所以,这种依赖于线性扫描的点分治,一定要先把子节点按照大小/深度排序。
1 | #include "lucida" |
对我来说算神题了
先考虑暴力做法
枚举每个与模式串的最后一个颜色相同的点,判断是否合法。
判断方法是从这个点向两边分别贪心地匹配两个模式串,找到一个最靠右的左串匹配位置l,和最靠左的右串匹配位置r。然后在[1,l−1]和[r+1,n]中找一下是否有两个颜色相同的点。至于找相同,一种方法就是遍历[1,l−1],把途径的颜色用bool[]标记,然后再遍历[r+1,n]。
可以看出,这样又做了很多无用功,因为很有可能连续的一段区间,向左右匹配的位置都是相同的。这样就不好了。
所以考虑对这个量进行预处理。然后我就怎么也想不出来了。研究一番Claris巨神的代码,并看不懂复杂度。写完A掉之后,忽然灵光一现,不禁拍案叫绝。
设原序列为c[],左模式串的序列为x[]。假设当前在计算ls[],ls[i]表示使得c[]的[a,i−1]中存在一个子序列x[],且最大的a,设first[p]表示在c[]中的第一个颜色p的位置,next[i]表示序列c[]中下一个与c[i]颜色相同的位置。
外层循环从[1,n]枚举c[]的每一个位置i,设f[p]表示使得[a,i−1]能成功匹配x[p..x.length]的最大的a。
然后循环这样做:
(在代码中,lx==x.length−1)
1
2
3
4
5
6
7
8
9
10
11
12
13for(int i=1;i<=n;++i) {
ls[i]=f[1];
if(c[i]==x[lx]) {
f[lx]=i;
for(int u,p=lx-1;p;--p) {
if((u=(f[p]?next[f[p]]:first[x[p]]))<f[p+1]) {
do f[p]=u;
while((u=(f[p]?next[f[p]]:first[x[p]]))<f[p+1]);
} else
break;
}
}
}
如果x[]的倒数第二个字符与c[i]相同,那就更新f[lx]。然后p从lx−1开始枚举,将f[p]挪到最后一个在f[p+1]之前的颜色为x[p]的位置。 复杂度? 设x[p]在c[]中出现了t[x[p]]次,那么f[p]至多会向后移动t次。而x[]的元素互异,那么复杂度就是O(∑t[x[i]])=O(n) 处理出来之后,可以发现,随着枚举点i右移,ls[i]和rs[i]都是单调不降的。因此可以类比莫队,实现O(n)计算。 记得特判模式串为1的情况
唯有泪千行。。 KMP--一个指针的均摊--O(n) 双指针--两个指针的均摊--O(n) K指针--K个指针的均摊--O(n) 只注意到了匹配的第一个点位置的单调性,但没有进一步观察所有点位置的单调性
1 | #include "lucida" |
真的是叶节点!好吧当我没说
只要知道某一条边需要多少蚂蚁流过就可以了,然而这个流量的取值是一个区间,所以要维护一个区间。一开始naive只维护了最小值。
如果知道了一个点的树边上需要流[l,r],那么这个点就需要有[deg[u]∗l,(deg[u]+1)∗r−1],它下面的边需要有[(deg[u]−1)∗l,(deg[u]−1)∗(r+1)−1]。
注意,即使不乘k,合法的群数也会爆int。
1 | #include "lucida" |