考虑哈希,为了避免冲突,需要多取几个数。
注意结构体与map的用法。
1 |
|
与一元一次方程可以解出来变量的具体值不同,微分方程式是关于函数的方程,解出来的是一个函数。例如微分方程
其中是积分常数(積分定数)。如果有一个初期条件是,即可解得。
这篇文章里会从零介绍一階微分方程式、二階微分方程式、偏微分方程式的解法,只需要掌握基础求导和积分的知识。
一/二階微分方程式:方程中最高是求一次或者二次导(微分),例如就是一个二階微分方程式。
常微分方程式:方程中只有一个变量,例如变量只有的方程式。
偏微分方程式: 方程中有两个以上的变量,例如包含和的方程组。
为了方便描述,
假设你不会解这个微分方程,只想知道函数在某个点近似的值,可以使用欧拉法近似。
对于每个点来说,把带入微分方程式中,可以计算出的数值,这个值其实就是函数在点的斜率。
那么可以从设定一个长度,从初始点开始,计算出横坐标所对应的的值;如此往复,直到计算出目标值。
显然,$h$的值越小,需要的计算次数越多,得到的结果越精确。
例:对于微分方程式
初期条件,令,计算的值。
解:可以画出下图的表格,方便计算。
代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using namespace std;
const int maxn = 1e5 + 10;
double x[maxn], y[maxn], A[maxn], h = 0.1;
double F(double x, double y){
return - (y / x) + 3 * x;
}
int main(){
x[0] = 1.0, y[0] = 4.0, A[0] = 0;
printf("y(?)=?\n");
scanf("%lf%lf",&x[0],&y[0]);
printf("h=?\n");
scanf("%lf",&h);
printf("ask for y(?)\n");
double End;
scanf("%lf", &End);
A[0] = F(x[0], y[0]);
int times = int((End - x[0]) / h);
for (int i = 1; i <= times; ++i) {
x[i] = x[i-1] + h;
y[i] = y[i - 1] + A[i - 1] * h;
A[i] = F(x[i], y[i]);
}
printf("-------------------------------------\n");
for (int i = 0; i <= times; ++i)
printf("%d %lf %lf %lf %lf\n", i, x[i], y[i], A[i], h * A[i]);
printf("y(%lf)=%lf", End, y[times]);
}
对于方程
我们可以把y和x有关的项分离,放到等号的两边,然后积分。
假设
例:
解:
如果和的次数一致,可以用替换,把方程化简以后可以使用変数分離法求解。
例:
这里引入一个新变量
带入方程中
对于以下形状的微分方程式
这里,积分因子u(x)定义为
如果把方程两边乘上积分因子,发现方程式左边其实变成了在微分以后的形式
例,在变数分离法的例题上用积分因子法,也能得到同样的结果
解:
1 |
|
1 |
|
\sum{i=1}^{M}i\times A_i = \sum{i=L}^{R} i\times Ai-(L-1)\times \sum{i=L}^{R} A_i
$$
INFに注意する。
1 |
|
Cのような直接計算するのは無理。
動的計画法を行う。
時間計算量。
1 |
|
ダイクストラ法のように、プライオリティーキューを使うことで で解くことができる。
もう一つの方法は、答えを二分探索で行いう。スタックでコストはX以下の頂点を入れる。
1 |
|
木の直径を利用する。
木の直径の両端の頂点を とおく。
このとき、任意の頂点 に対し、 のいずれかは から最も遠い点である。
1 |
|
Rating:
正常发挥,甚至打的还挺好(因为F题比较难所以大家都是ABCDE),performance第一次上2000,虽然上分还是很缓慢。F题比赛上写出来的可能性不大。但是其实写前面几道题可以再快一点,不过根据我现在的水平,还是比较满意的。
C题WA了一次的原因是没看到输入的数据可能有负数。(其实把x=read()
改成x=abs(read())
就能过了),还是属于看题不认真(这个和英文水平低下好像没有关系)。
然后D题又想当然了,一个n个点n条边,每个点出度入度都为1的图不一定是一个环。可能是多个环。(详见APIO2018 铁人三项)。2018年搞不清白的东西现在还搞不清白。
每个环单独做一次就好了,还好样例里面有两个环的情况不然可能就wa死在上面了。改代码花了点时间。
然后E题是个简单DP,好在昨天CF里面刚做了个简单DP,今天就知道有DP这个东西了。看题和写花了十几分钟。
Rating.png:
正常发挥吧。毕竟就这水平。
AB没啥好说的,读题速度要是快点就好了。
C题不知道在想啥。想了一堆奇怪的东西(觉得区间是不交,是包含的,然后求每个数在的最小满足条件的区间就好了),然后发现这东西就是个错的(eg:03030)。莫名其妙花了好久,数组没清对还WA了一下。
D题做的挺慢的,想当然写个贪心然后WA。然后想了好久把才贪心叉掉。(eg: R:5 G:4 B:4,3)。就是两个较小的数相同的时候,选其中的一个是不对的。(R和G选了,B自己剩下两个没法配对)。然后改了个DP花了半个小时,虽然水的没办法,不过因为好久没写过DP了,勉强也能接受。
然后就只有半小时了,读了E和F。感觉比较麻烦,比赛时间是写不完了。但是应该都是可做题,这几天找个时间写一下。
发现最近学习效率都不太行,还是要高效一点。
Day2:发现E题看错了,以为是有负数收益的法术。草,有SB。
有两种法术,火焰法术造成一定伤害,雷电法术造成一定伤害还会使得下次伤害翻倍。
现在一个人啥也不会,每次学会一个法术或者忘掉之前的一个法术,问用现有的法术如何打出最大伤害(每个法术的收益大于0)。
如果有个雷电法术,那么翻倍的一定是目前会的伤害最大前个。除非前个全是雷电法术,那么放最前面的第一个雷电法术是不能翻倍的,这个时候还不如把一个最大的火焰法术放进去翻倍(也就是用最大的火焰法术换最小的雷电法术)。
支持插入和删除,要求维护:
知道这些东西以后,就可以判断是否都是雷电法术。如果不是都是,答案就是前大×2+后面的和;
如果都是,答案就是前大×2+后面的和-最小的雷电法术+最大的火焰法术。
平衡树
这些东西可以用数据结构去维护。平衡树维护子树和,树枝数组要离线和二分, 线段树维护子树大小和子树和然后二分。
这个是直接的做法。
std::set
基于每次的最多加/减,其实也有用set的维护方法。
分别用两个set表示前大的数和剩下的数。
如果要改变的话,因为每次改一个,要么把第二个set的最大值丢到第一个set去,要么把第一个的丢到第二个里面去。
插入的时候,看插到哪个set里面去。
删除的时候,看是从哪个set里面删的。
顺便维护每个set中所有元素的和。
1 |
|
给出一个长为n的由01?三个东西构成的字符串。
?可以变成0或者1。
字符串中每有一个连续k个0或者k个1(长为k的连续段),答案就能加一,但是这些段都不能重合。
(题目把是连赢k次(对应字符串的1),或者连输k次(对应字符串的0)叫做一轮,问最多能有多少轮)
对于每个k从1到n,求最大答案。
比赛的时候感觉这题有点数学,想到了按照相同长度的去处理,复杂度nlogn,然后就不会做了= =
实际上除了复杂度以外啥都没想对。
这种题一起不好算,可以在之前的基础上考虑一个一个加,也就是按照顺序一个一个算,复杂度慢慢变小。
显然答案的和是级别的。
可以有一个的做法就是先计算二分开始的最长的可能,然后排序,从大到小把每个位置加进一个set
,每次计算的时候lower_bound(i+size)
。
这样做其实多算了不必要的信息。
我们假设我们现在在点,要求长度为,暴力地比较了是不是有可能满足条件,一直找到了一个满足符合条件。
根据这题的特殊性质:
那当我们算长度的时候,之前算过的不满足条件的区间后面再加一个数字也不会满足条件。这些区间也不可能是答案
(因为在长为的时候就不是全或者全,的话更不是了)
我们直接去看。
用nextQ[i]
表示如果i
不行,下一次应该从哪个位置开始。
当长度为算完后,nexQ[i]
表示从i开始的最近的一个符合条件的区间的头。
这样就是了。
1 |
|
SB.jpg:
A题瞎写WA一次;B题瞎写FST on Test45,最后发现自己的算法假的离谱,一个5×5的矩阵就能叉掉(就算如此还混过了pretest);D题瞎写,计数的时候要把所用东西加起来,ans的初始值写-1(写0就行了),而且在莫名其妙的位置还爆了个int(两个1e5乘起来了)。
下次要注意细节了,不能再想当然。
给出一个矩阵,有一个棋子(走法同国际象棋中的车),给出矩阵的大小和棋子的初始位置,输出把每个位置走一遍,要求输出路径。
这题没啥好说的,看代码就行了。
1 |
|
比赛的时候写的假算法是一直往一个方向走,然后把种方向的排列都试验一下。
然后愉快的FST on test45
错的原因是没用到这个题的性质,然后我发现有些情况按照我的想法是没有可行的方案的。
那么有了这么一个问题:
如果这个棋子每次只能走到相邻四联通的位置(上下左右),那么什么情况下是不存在走法的。
首先给一个结论:只要是长和宽都是奇数的矩阵就不行。
比说说给出3×3的矩阵,规定你起点是2 3,就不存在方案。
你可能觉得是因为起点在最外面一圈所以不行(因为这题规定起点不会在矩阵的最外圈)。
其实给出5×5的矩阵里面打叉这些位置作为起点都是不行的。
事实上这属于一个哈密顿路径问题,是一个图论问题。
(英文资料都啃不动,看的全是中文的)
哈密顿通路(Hamilton path):经过所有顶点的初级通路(经过所有点有且仅有一次)
哈密顿回路(Hamilton circuit/cycle):经过所有顶点的初级回路
哈密顿图(Hamiltonian):有哈密顿回路的图
半哈密顿图(semi- Hamiltonian):有哈密顿通路的图
无向图是哈密顿图,则的任意非空真子集有
其中表示图G删去V1后的联通分支数。
如果是半哈密顿图则有
由于没学过太多的图论知识,这个东西好像不太好证明,但是可以考虑:如果是Hamiltonian/semi-Hamiltonian删除相邻的,剩下的联通数一点是最大的,但是不会大于|V1|。
知道这个以后,可以发现,在长宽都是奇数的矩阵里面,只要删除个点,那么矩阵中剩下个点,但是为了规定起点,所以要新建一个点,只要这个点链接了被删除的点中的一个,那么就又多了一个联通分支,也就是说比删除的点的个数多了个,这个不符合半哈密顿图的必要条件。
正因如此,类似这种(1,2)(2,1),在行列都为奇数的矩阵中,以行列和为奇数的点作为起点,不存在哈密顿路径。
在1976年前,所有方法都是对称加密算法。
Alice想出一种可逆加密方法,把加密方法告诉Bob,Bob加密了消息把密码发给Alice,Alice根据加密方法解密。
要是别人截获了消息和加密方法,或者根据密码推测出加密方法,那么就能获得密码信息。
1976年,Diffie–Hellman_key提供了一种非堆成加密的思想。
Alice有两个密钥,公钥和私钥,把公钥告诉Bob,Bob加密了把消息把密码发给Alice,Alice用私钥解密。
非对称加密方法的好处是私钥是保存在电脑的,也就是无论别人窃取什么东西,没有私钥也无法解密。
RSA算法就是一种现在普遍使用的非对称加密算法。
凭空变出两个质数$p,q$。
设$N=p\times q$
根据欧拉函数$r=\varphi(N)=(p-1)\times (q-1)$
凭空变出一个小于$r$的整数$e$使得$e$和$r$互质。
求$d$使得$ed \equiv 1 \pmod{r} $
销毁$p,q$,没有人知道$p,q$是多少。
得出$(N,e)$是公钥,$(N,d)$是私钥。
Alice得到公钥和私钥,Alice把公钥告诉Bob,自己把私钥藏起来不告诉任何人。
Bob给Alice发消息。
假设Bob的消息是$n$,密码是$c$,手上有公钥$(N,e)$
Bob将加密后的$c$发给Alice
Alice得到密码c,根据私钥$(N,d)$
解密得到$n$
因为$e\cdot d\equiv 1 \pmod{r}$,即$e\cdot d\equiv h\cdot r+1\pmod {N}$,其中$h$是自然数。
式子又可以写成
根据欧拉定理$a^\varphi(n)\equiv 1\pmod{n}$
假设窃取者得到了$(N,e)$以及加密后的密码$c$,没有获得Alice的私钥$d$。鉴于多次剩余并不可求,目前唯一已知的(已公布的)方法就是将$N$分解质因数。
将$N$分解质因数得到$N=pq$,然后可以得到$r=\varphi(N)=(p-1)\times (q-1)$,根据$ed\equiv 1\pmod{r}$得到$d$。
所以目前认为只要$N$够大,黑客就没办法了。目前推荐的$N$的长度至少为2048位,不然可以很快分解。
已经证明量子计算机可以在多项式时间内进行因数分解。
如果量子计算机成型,RSA算法将被淘汰。
首先生成$N$,方法是用一个非常好的,而且没有被发表的方法(不会被窃取)随机生成一个看起来像质数的大数,然后用概率算法(类似Miller-Rabin)检验是否是质数。如果能通过测试,则进行精确的测试确保是一个质数。
$p$和$q$不能太靠近,而且$p-1$和$q-1$的因子不能太小。
$d$必须大。1990年有人证明$q<p<2q,d<\frac{1}{3}N^{\frac{1}{4}}$,那么很好算出$d$。
以上证明我都不知道怎么证的,但是了解一下就好。
e=2永远不能用。
由于要求很多以及计算过程比较复杂,RSA比较慢。实际运用(如TLS)是结合了非对称加密(如RSA)和一些对称加密(AES)
假设Eve在Alice和Bob中间传话,把Alice告诉他的公钥独吞,告诉Bob自己的公钥,然后传递信息的时候先解密Bob的信息然后加密发给Alice,那么Alice和Bob甚至不知道有人窃取了信息。
现在人们通过一个可信的第三者来解决这一问题。即证书中心,一个可信的数字认证机构将用户的个人身份和公钥连接在一起,确定这个公钥就是Alice的而不是Eve窃取以后更改的。这个问题的前提是人们信赖第三方机构。
因数分解
2009年 RSA-768(768bit)被成功分解
时间攻击
Even要是知道Alicze对特定密码的加密时间以及了解Alice的硬件设备,可能可以根据加密时取模的时间计算,因为1比0花的时间要多,可以推算出$d$。
给个数字,选择个数字乘积最大,输出答案
如果是偶数个数相乘,一定是最大的。按照正数和负数分开算的话有点麻烦,不如按组单独算。先把数组排序,可以证明答案是连续段(最多两段),如果有左边的段,那一定是以最小值开头,有右边的话一定是以最大值结尾的。
证明如下:
首先,选择中间的数一定不如极值,因为无论怎样最大最小值中至少有一个比中间的数好。所以段一定是以最小值开头或者最大值结尾的。
如果两端都是正数/负数,显然合并成一段更优秀,这个时候就是一段。
那么一定是负数正数组成的段,考虑在中间选一个数(第三段)替换两段中的,同上,当然也不如极大/极小值。
所以可以假设现在是和,那么我们比较和的大小,决定是否。
需要注意的是,如果相等的话要继续比较,因为并不单调。eg: 负负 负负 … 负正 正正,
1 |
|
给一个小写字母字符串,随便在什么位置插入一个字符次,最后能得到什么字符串,取模。
直接计算非常难算,因为会有很多重复的情况。我们可以考虑作为一个子序列在新的字符串中第一次出现的每个字母的位置,这样计算的话不会有重复。
首先先固定每个字母作为子序列出现的位置。对于位置非的位置,如果在之前,那么不能填下一个的字符(因为填了的话S的子序列位置就是了);如果在之后就可以随便填。那么就是。
那么枚举,那么确定 的新位置是,剩下的个数在个位置里面选,乘上就行了。
1 |
|
有个骆驼排队,第个骆驼要是能排在前个则能获得都愉悦度,不在前个获得的愉悦度。如何排队才能获得最大愉悦度。
首先可以先把加到答案上,这样的话把骆驼分为两类,如果,排在前的获得;如果,排在后的获得愉悦度。
可以发现这两种骆驼是可以分开算的,因为一个区间是一个区间是,如果占用了位置,可以把两个骆驼交换位置。
那么按照从大到小,贪心的计算。
1 |
|
给出字符串和,每次进行如下操作:
选择A中的几个位置,要求这几个位置是一样的字母。即选出满足是同一个字母。然后把这些全部替换成,要求。
求把变成的最小操作次数。
可以按照字母划分,根据字母变成字母,可以建立一个图出来。然后可以发现,一个大小为的联通块只需要次就能完成,用并查集计算即可。
1 |
|
定义一次操作把的位置变成,求把一个数列排序的操作方法,最多能操作次(不需要最小次数,可能无法排序),。
可以从小到大对把第i个数放到第i个位置。把向左移动两次需要一次操作,向左移动一次需要两次操作。这样操作以后,如果最后两个数没有排好就是不行。
对于每个数字不同的序列这个结论成立。
但是比如说 1 2 3 3 6 4
可以交换相同的数字的位置,并且排序后面的6 4
,这个结论就有问题了。
考虑到排序其实是消除逆序对,一次操作一定要么没改变,要么增加/减少了两个逆序对。
把a b c
变成c a b
,相对于其他的数位置关系没变,改变的是c
和a b
之间的位置关系。
如果a,b<c
或者c<a,b
那么一次操作改变了两个逆序对,如果c
在ab
中间,那么没改变。
所以,如果有偶数个逆序对,一定可以排序。
如果是奇数个逆序对,而且如果没有相同的数,就不能排序。
但是,有相同的数的时候,可以把随便一对相同的数看成一个逆序对,构造出偶数个逆序对,就能排序了。
1 |
|
用表示莫比乌斯函数。
一个有用的性质
证明:考虑相当于在的因子中选择若干个,然后把这些因子组成的数的加起来,如果有一个数的次数大于,对答案没有贡献,所以这个值只与选择的质因子的个数的奇偶性有关,也就是说只考虑每个质因子是否选择。
1 | IL void Sieve() { |
记为数论函数,定义
狄利克雷卷积满足交换律,结合律和分配率。
对于数论函数
然后得到了
对于另一个式子
设表示集合中的倍数的数的的个数,表示的数的个数(我们要求的答案是)。
小技巧:
记为的约数个数,因为
对于所有,预处理。
预处理可以枚举质数然后用调和级数,同时也可以在线筛的时候算。
考虑,加入会让,之前的变为,贡献为,
如果,加入让除了的所有因子上有一个,此时
同上,我们可以预处理
由于是积性函数,也是积性函数,所以是积性函数
对于,由于以上都是0,对答案无贡献。
一般地
考虑右边式子,由于是,所以只有种不同的取值,是一个前缀和的性质,于是可以递归求,求的过程中用hash表实现记忆化防止复杂度退化。
时间复杂度
注意到比较小的时候可以线性筛出来。
根据均值不等式当有最低复杂度
1 | int sieveMu(int n) { |
二次剩余
定义:
当存在某个,式子成立,称"是是模下的二次剩余"。
性质:
考虑一种判断质数的方法,根据费马小定理,。为了检测是否为质数,可以带入几个数,判断费马小定理是否成立,如果都成立,那么可以认为这个数是质数。
但是有一些数没办法判断出来,如,这一类数叫做Carmichael数(卡迈尔克数)。这一类数虽然不多,但是在内还是有个,会严重影响算法的正确率,Miller-Rabin这个算法便诞生了。
设要检测的数为,为奇数。
根据,如果是质数,的在模下二次剩余只有和。例如,,反之对于非质数会有更多其他的数,例如,,说明是非质数。
对于不被整除的一个数,如果为质数,根据费马小,有。
考虑这个是怎么来的,那么根据二次剩余,下面两个条件之一必然成立。
于是我们检测到第一个,那么。如果不是,则说明不是质数。所以可以随机一个底数,或者是取前几个小质数作为。
对于一个奇合数,它的证据个数有个,对于一个证据,检测出的概率是。如果选择了个底数,那么正确的概率约为。
对于的范围的数,取前12个质数就可以正确了。
1 | int pri[] = {2, 3, 5, 7, 11, 13, 17, 19, 21, 23, 29, 31, 37}; |
生日悖论,23个人中两个人有相同生日的概率大于。
对于值域为,出现至少两个相同的数的概率为
对于一个非质数,存在和使得,那么递归分解和就可以实现质因数分解。
假设是的因子,暴力地可以随机一个数然后判断是否为,这样找到的概率为实在太小。如果得到了两个数和,其中且。如果不为或,那么可以得到 的一个非平凡因子。
往一个纸带上填数,值域为,出现有两个数就停下(显然步数最大为),期望的长度是多少?一共存在对数,而每队相等的概率为
我们可以算出的期望长度为,也就是在模意义下的期望长度为。
对于一组,的概率为,根据生日悖论,如果选出大于组数,其中有两个相等的概率大于,而后会更加快速地递增。
如果我们每次合理的随机选择,使得不相等,在大小为的集合中,选出合理的概率为,因为集合大小为,那么找到相等的概率为,也就说。
我们构造一个随机的函数使得在意义下几乎随机,不断将变为,就会变为一个随机的数,经过测试,当的时候很合适。经过若干步以后一定会走到一个定长的环中。最开始设为随机数,在每一步的操作中把变为,变为,检查。这样每次比多走一步,经过环长步以后会到达相同位置。
因为对于每一步都要求一遍,那么复杂度为。
1 | IL ll func(ll x, ll mod, ll a) { return (mul(x, x, mod) + a) % mod; } |
但这并不是能达到的最优复杂度,可以优化为。
首先的写法可以加上一点常数优化。
1 | IL ll gcd(ll x, ll y) { |
根据奇偶性,每隔两次至少要除,而换成非递归会快一点。
我们可以注意到, 设定一个步长,固定,一共跳步,把每一步的相等然后取。虽然还是遍历了个元素,但是每隔次取一次gcd可以降低复杂度,使得它为。
如上图所示,图中的将从跳到的位置,因为之前已经算过蓝色部分并没有得到结果,那么因为图中的红色部分和蓝色部分等长,重复算红色部分没有意义,可以直接跳到现在这个位置;现在从图中的位置开始,每次跳步,把每一步的乘起来然后和取检验。同样的,如果不是或者是说明找到了直接return;如果是,视为没有找到;如果是,可能这个是由前面的几个数相乘得到的,也可能是走完了一个环,此时,那么我们需要再走一遍这么长,判断中间是否出现结果,找到答案然后返回。
具体实现看代码(unsigned long long和__uint128_t真快)。
1 |
|