此题是求一个数字序列中,长度为3的子序列(a,b,c),且满足条件a<=b<=c或者c<=b<=a的子序列的个数。
明显枚举每个b,求每个b左边的a的个数和右边c的个数,以及左边c的个数和右边a的个数,然后累加左右乘积求和即可。刚开始只求了满足条件a<=b<=c的部分,而且忘记用64位了。wa了几次。求左边a的个数其实就是求小于等于b的数字的个数,这个刚好可以用树状数组或者线段树求。具体见代码。
代码如下:
1 | #include <stdio.h> |
STEP BY STEP
此题是求一个数字序列中,长度为3的子序列(a,b,c),且满足条件a<=b<=c或者c<=b<=a的子序列的个数。
明显枚举每个b,求每个b左边的a的个数和右边c的个数,以及左边c的个数和右边a的个数,然后累加左右乘积求和即可。刚开始只求了满足条件a<=b<=c的部分,而且忘记用64位了。wa了几次。求左边a的个数其实就是求小于等于b的数字的个数,这个刚好可以用树状数组或者线段树求。具体见代码。
代码如下:
1 | #include <stdio.h> |
这个题意思是翻转一个01立方体。翻转多次后再查询某个点的值。
还是利用上一篇文章的思想,把翻转操作转换为单点更新操作。把查询操作转换为利用树状数组查询和的方式。这样每次操作的复杂度都是logN的3次。而直接翻转立方体的复杂度是N的3次。
这个题最麻烦的地方是空间想象能力。因为要翻转8个点才能完成一次立方体翻转。比如,翻转(x,y,z)相当于以该点作为左上角做一个无限立方体,把该立方体翻转。这样就会翻转多余的部分,那么需要把多翻转的部分翻转回来。最后的思考结果发现,只要对每个顶点翻转一次即可。至于为什么这样,自己去计算重复翻转的部分就会明白了。刚好确实是把每个点翻转了一次。
代码如下:
1 | #include <stdio.h> |
这个题的意思是给定一个长为N的区间。不断的给某个子区间[A,B]中的每个点涂一次色。最后问每个点的涂色次数。
这个题貌似可以扩展到多维的情况,但是多维的情况下必须用树状数组求和以加快速度,一维的情况直接求和即可。
假如,第一次涂色是对区间[A,B]涂色一次,可以让nNum[nA]++,nNum[nB+1]—即可。因为这样对于区间[0,nA-1]的任意值i有都要nNum[1]+nNum[2]+…+nNum[i] = 0。而对于区间[nA,nB]的任意值i有nNum[1]+nNum[2]+…+nNum[i] = 0。对于区间[nB+1, nN]的任意值i有nNum[1]+nNum[2]+…+nNum[i] = 0。那么重复多次了。如果上述求和nNum[1]+nNum[2]+…+nNum[i] 刚好代表每个结点i的涂色次数,那么这个题就可解了。
用例子验证一下,发现肯定是这样的。证明略了。至于树状数组网上一大堆资料。树状数组模板单一,敲代码太方便了。
代码如下:
1 | #include <stdio.h> |
这个题是求次短路。有个不错的解法,是根据一个结论,替换调最短路里面的一条边肯定能得到次短路。
那么,只要枚举所有边就可以了。比如,假设开始点为s,目标点是d,设最短路为dis(s,d)。对于边(u,v),dis(s, u) + w(u, v) + dis(v, d) 大于dis(s, d),则该路径就可能是次短路。求出最小的大于dis(s,d)的值就可以了。方式是从s开始和从d开始进行2次单源多终点最短路径算法。然后枚举边即可。
该算法可以这样理解。因为替换最短路径里面的边,路径的长度只会变大或者不变。如果存在让更短路径变小的边,这本身就与最短路径是矛盾的。所以替换2条或者更多的边只会让路径变得更大。因此,只需考虑替换一条边的情况即可。
代码如下:
1 | #include <stdio.h> |
这道题的意思是给定一个长N的整数序列,用一个大小为K的窗口从头开始覆盖,问第1-第N-K次窗口里面最大的数字和最小的数字。刚开始还以为优先级队列可以做,发现无法删除最前面的元素。估计用线段树这个题也是可以解得。用这个题学了下单调队列。
单调队列正如其名,是一个从小到大排序的队列,而且能够保证所有的元素入队列一次出队列一次,所以平摊到每个元素的复杂度就是O(1)。
对于这个题单调队列的使用。以序列1 3 -1 -3 5 3 6 7举例。
1)元素类型:一个结构体,包含数字大小和位置,比如(1,1),(3,2)。
2)插入操作:从队尾开始查找,把队尾小于待插入元素的元素全部删除,再加入待插入的元素。这个操作最坏的情况下是O(n),但是我们采用聚集分析的方法,知道每个元素最多删除一次,那么N个元素删除N次,平摊到每一次操作的复杂度就是O(1)了。
3)删除队首元素:比如本文给的那个题,窗口一直往后移动,每一次移动都会删除一个元素,所以很可能队首会是要删除的元素,那么每次移动窗口的元素要进行一次检查,如果队首元素失效的话,就删掉队首元素。
代码的实现,我是包装deque实现了一个模版类。速度很不好,居然跑了11s多才过,幸亏给了12s的时间,看status又500多ms就过了的。估计数组实现会快很多。
代码如下:
1 | #include <stdio.h> |
这是一个动态规划题,据说是背包问题的变形。我动态规划做得很少,解法一直按照算法导论的思想分解重叠子问题。
题意是用钱尽可能多的买物品,每种物品买一个,问有多少种买法。
我也想不出这是什么背包问题的变形,没做过几个背包问题,也没看过背包九讲。还是坚持认为正确的用状态描述成子问题就一定能解题的。今天和队友在做专题时候做到这个题,我一直做了一上午都没出来。
后面发现了个性质就可以直接转换为类似最简单的背包问题了。排序物品价值,从最大物品开始分解子问题,用剩余物品数和钱描述问题的状态。当前物品是否必须取,是根据当前的钱把剩下的物品全买了之后剩下的钱还是否大于当前物品的价值,如果大于就必须买,否则可以买或者不买。
为了正确描述问题的状态,必须事先排序价值数组,因为排序之后可以保证不能买当前物品的时候一定不能买前面的物品,那么我们对前面物品的处理就是正确的了。至此可以进行最简单的子问题分解了。到最后物品处理完之后(物品数为0),如果钱一点都没减少,那么(0, M) = 0,否则(0, M) = 1。注意这个边界处理,否则会wa。所以,需要先对价值数组排序,并计算出表示前N个物品价值和的数组。
做不出来的时候,翻了下别人的解法,一头雾水。看来还是算法导论的思想指导意义大多了。。。
代码如下:
1 | #include <stdio.h> |
题意比较纠结,搜索了把题意。
给你一个素数P(P<=30000)和一串长为n的字符串str[]。字母 ‘*’ 代表0,字母a-z分别代表1-26,这n个字符所代表的数字分别代表f(1)、f(2)….f(n)。定义: f (k) = ∑0<=i<=n-1aiki (mod p) (1<=k<=n,0<=ai<P),求a0、a1…..an-1。题目保证肯定有唯一解。
解题思路:高斯消元。根据上面的公式显然可以列出有n个未知数的n个方程式:
说下所谓的高斯消元的思路,其实可以参看维基百科,
代码如下:
1 | #include <stdio.h> |
这个是求扩展离散对数问题。XY mod Z = K,给出X,Z,K,求Y。
当Z是素数的时候直接用baby-step算法即可了。但是,模数不是素数的情况怎么办了。
方程a^X = b % c,可以进行一系列的转化。假设d = gcd(a,c),由a^(x-1) a = b % c,知道a^(x-1)要存在必须满足gcd(a,c) | b,如果满足这个条件,那么我们可以在方程2边同时除以d,方程是不变的。因为a^x = b + k c,再除以公约数d,得到方程a^(x-1) a / d = b / d + k c / d。根据以上推论,我们可以不断的除以d,直到gcd(a,c)=1。
假设我们除了k次,那么方程转化为a^(x-k) a^k/d^k = b / d^k + k c / d^k。令d = a^k/d^k,b’ = b / d^k,c’ = c / d^k,x’ = x - k,方程转化为a^x’ d = b’ % c’,得到a^x’ = b’ d^-1 % c’。
现在直接用baby-step解方程a^x’ = b’ * (d^-1) % c’即可。注意到x=x’+k,如果存在x小于k的解,那么x’小于0,但是由baby-step是不会求负的次数的,所以需要先枚举一下是否存在小于k的解,由于输入的数据不会超过10^9的,假设k不超过50进行枚举即可了。
代码如下:
1 | #include <stdio.h> |
这个题很奇葩了。题意是给出个数字L,假如存在一个数K使得L K = 888…,求888…的最小长度,如果不存在这样的K,那么输出0。我是什么思路也没有了,拖了几天了,数论搞死我了,只能找答案了。
我看到个比较靠谱的推法。首先,888… = 111… 8=(10^0+10^1+…+10^m-1) 8=(10^m - 1) / 9 8,PS:m代表888…的长度。
好吧,终于化成指数了,现在有8 (10^m-1)/9=K L,最小的m就是我们要求的答案啦。
方式1:
=> 8 (10^m-1) = 9 k L
=> 8 / d (10^m-1) = 9 k L / d,d=gcd(8,9L)
=> 10^m-1 = 0 % 9 L / gcd(8, 9L) = 0 % 9 L/gcd(8,L),(由于gcd(8/d,9L/d)=1,那么10^m-1必然是9 L / d的倍数了)。
=> 10^m = 1 % 9 L / gcd(8,L)
方式2:
=> 8 (10^m-1)/9 = 0 % L
=> 8 (10^m-1) = 0 % 9 L(这步的推出,比如x/9 = k n,那么x = 9 k n了,显然成立)
=> 10^m-1 = 0 % 9 L / gcd(9 L,8),假如,d = gcd(9 L,8),那么有8 / d (10^m - 1) = k 9 L/d,因为8/d不可能是9 L / d的倍数,所以10^m-1必定是9 L / d的倍数,所以10^m-1 = 0 % 9 L / gcd(9 L,8)),=>,10^m - 1 = 0 % 9 L / gcd(L, 8),
(因为gcd(9,8)=1)。
=> 10^m = 1 % 9 L/gcd(8, L)
至此,2种方式都推出了,10^m = 1 % 9 L / gcd(8,L) 。
那么怎么解答这个问题了,这个就用到了欧拉定理了。令p = 9 L / gcd(8,L),那么有10^m = 1 % p。由欧拉定理知,Z p中所有的数字a均满足a^euler(p) = 1 % p。那么,10只要是p的乘法群中就肯定有解了。如果,10不在Z p中了,肯定是无解的。证明如下:
由a^x = 1%p,可以得到a^(x-1) a=1%p,要a^(x-1)存在,那么gcd(a,p)|1,那么gcd(a,p)必须是1。综上所述,要满足式子a^m=1%p,必须gcd(p,a)=1,即a必须是p的乘法群中的数字。现在的问题是求最小的m,由欧拉定理知道a^euler(p)=1%p,m再大就开始循环了。但是m可能会更小。比如,我们现在知道最小的m是min,那么有a^min=1%p,因为要满足a^euler(p)=1%p,那么a^euler(p)肯定能变换成(a^min)^k,至于k是多少就不知道了,当然也可以求出来。那么min就是euler(p)的一个因子,而且是最小的一个满足a^min=1%p的因子了。
现在就可以通过枚举euler(p)的因子,找到最小的因子min满足式子a^min = 1 % p就能解决本问题了。注意求a^m%p肯定是通过算法导论上面那种方法的,O(32)或者O(64)的复杂度,还有a b%m也需要自己模拟,因为可能a * b就溢出了。
代码如下,貌似代码还可以通过其它的改进加快速度。
1 | #include <stdio.h> |
此题的意思是分解大数字,数字的范围是Longlong级别的,好像不能暴力的样子。但是,题目给出了个条件,最多只有一个因子的大小超过1000000。哈哈,这就是暴点啊。既然,如此直接枚举1000000以内的因子就行了,剩余的部分如果大于10的6次肯定是N的因子了,就不用暴力了。如果小于10的6次肯定是1啦,因为2-1000000的因子都被处理了啊。
这样这个题就不会超时了。确实,暴力是需要技巧的。还要注意uva上要用%lld输入。
1 | #include <stdio.h> |