emmmm
,
NOIP2017
就要来了,还是思考一下怎么复习吧~
打模板或许是一个很不错的选择~
好了我们就响应号召,努力打模板吧~做一个优秀的背板先生(划掉)~
Round 1
-数学
1.质因数分解
虽然水,但还是很有用的~
num[i]
表示第
i
个质因数,
int m=int(sqrt(n)+0.5);
for(int i=2;i<=m;i++)
{
if(n%i==0)
{
num[++cnt]=i;
while(n%i==0)
sum[cnt]++,n/=i;
}
}
if(n>1)
num[++cnt]=n,sum[cnt]=1;
2.线性筛质数
很有用的玩意儿,可以很快地刷出所有质数,有些题很好用,
for(int i=2;i<=n;i++)
{
if(!H[i])
P[++cnt]=i;
for(int j=1;j<=cnt&&i*P[j]<=n;j++)
H[i*P[j]]=1;
}
3.线性筛欧拉函数
这个好像不是很常用,理论上也不是
phi[1]=1;
for(int i=2;i<=p;i++)
{
if(!phi[i])
{
for(int j=i;j<=p;j+=i)
{
if(!phi[j])
phi[j]=j;
phi[j]=phi[j]/i*(i-1);
}
}
}
4.质数判定-
超牛逼的算法,可惜并不完美,所以要多测几次……
pow(i,j,k)
表示
ij % k
的值,
u
是
for(int w=1;w<=S;w++)
{
int a=rand()%(n-1)+1;
int x=pow(a,u,n);
for(int j=1;j<=t;j++)
{
int y=x*x%n;
if(y==1&&x!=1&&x!=n-1)
{
printf("No\n");
return 0;
}
x=y;
}
if(x!=1)
{
printf("No\n");
return 0;
}
}
printf("Yes\n");
5&6.快速幂
+
模乘法
不想吐槽,为何现在才开始搞这个,跳过跳过不讲不讲
嘴上说着不讲,身体却很诚实
int pow(int a,int k,int p)
{
if(!k)return 1;
int q=pow(a,k/2,p);
if(k&1)return q*q%p*a%p;
return q*q%p;
}
int mul(int a,int b,int p)
{
int ans=0;
while(b)
{
if(b&1)ans=(ans+a)%p;
a=(a<<1)%p;
b>>=1;
}
return ans;
}
7,8&9.
卧槽这个真的不讲
.
.
.
.
.
看什么看,真的不讲……
10.中国剩余定理
很牛,但是一般不会考裸题,所以有时是一个取余合数时坑你的玩意儿
还记得某大佬的出的神题(简单的组合数学
DP
,但是有除法)
其中
D
代表模线性方程组中的模数(两两互质),
for(int i=1;i<=n;i++)
{
int x,y;
exgcd(tot/D[i],D[i],x,y);
x=(x%D[i]+D[i])%D[i];
sum=(sum+1ll*(tot/D[i])*x%tot*R[i]%tot)%tot;
}
printf("%d",sum);
11.卡特兰数列
不是很常用,其值就等于
h[0]=h[1]=1;
for(int i=2;i<=n;i++)
h[i]=h[i-1]*(4*i-2)/(i+1);
printf("%lld",h[n]);
12.康托展开式
fac[i]
表示
i!
,而
A
表示排列的元素,
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(A[j]<A[i])
sum+=fac[n-i];
printf("%d",sum+1);
13.线性求逆元
适用于求
[1,n−1]
模
n
意义下的逆元(当然如果其中有不与
inv[i]=-(n/i)*inv[n%i];
14.
Stirling
数(斯特林数)
用到的时候很少,但是一旦用到就可以恶心你半天(因为它最爱和组合数一起来猥琐你了,有时还会有
DP
,节哀自重吧)所以这样看来还是要写
S1[0][0]=S2[0][0]=1;
for(int i=1;i<=1000;i++)
{
S1[i][0]=0;
S2[i][0]=0;
for(int j= 1;j<=i;j++)
{
S1[i][j]=(S1[i-1][j-1]+1ll*S1[i-1][j]*(i-1)%mod)%mod;
S2[i][j]=(S2[i-1][j-1]+1ll*S2[i-1][j]*j%mod)%mod;
}
}
15.高精度加减乘除模
emmm,这个代码太长就不放了。原理都是知道的对吧?加法、减法、乘法只要模拟一下竖式就好了,其中加减
O(n)
,乘法
O(n2)
(我不会
FFT
,所以不能
O(n log n)
),除法我只会二分答案,所以更大,而取余倒是简单,
a%b=a−a/b∗b
即可
Round 2
-图论
1.链式前向星存图
超好用的存图方式!用数组代替链表,但是功能却并没有任何减弱,不仅内存只看边的数量(这是邻接矩阵所不能的),时间复杂度常数极小(这是
vector
所不能的),还可以直接调用第
i
条边(这是链表所不能的),我的图论题全都是写的链式前向星。
void add(int a,int b,int c)
{
++cnt;//边数
tar[cnt]=b;//到达节点
len[cnt]=c;//边长
nex[cnt]=fir[a];//模拟链表的指针
fir[a]=cnt;//模拟链表队尾
}
2.父亲-儿子-兄弟表示法存树
还是很巧妙的存树方式,可惜由于大多数时候给出的树一般都是无根树,所以使用机会还是不多
void add(int p,int q,int r)
{
b[q]=s[p];//兄弟
s[p]=q;//儿子
l[q]=r;//连接自己与父亲的边长
f[q]=p;//父亲
}
3.
虽然很纠结英文单词写对没有,但还是就这样吧……最好写的最短路,所以一般不考(尴尬),但是相较于其他单源点算法,能够实现的功能也多了很多(例如找出负环,多源点最短路)
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(A[i][k]+A[k][j]<A[i][j])
A[i][j]=A[i][k]+A[k][j];
4.
Dijkstra
(堆优化)
由于优先队列写着方便,所以我们就直接用优先队列吧(什么?我的写法内存玄学?不存在的)
void Dijkstra(int s)
{
priority_queue<node>q;
memset(dis,0x3f,sizeof(dis));dis[s]=0;
q.push(node(s,0));
while(!q.empty())
{
int p=q.top().x;q.pop();
if(vis[p])
continue;
vis[p]=1;
for(int i=fir[p];i;i=nex[i])
{
int v=tar[i];
if(dis[p]+len[i]<dis[v])
{
dis[v]=dis[p]+len[i];
q.push(node(v,dis[v]));
}
}
}
}
5.
SPFA
特别好写的最短路,也是最常用的最短路,不过因为时间玄学,所以可以被特殊数据卡成狗(不似
Dijkstra
100%
跑出
n log n
)但一般数据就像开了挂一样跑得飞快
int SPFA(int s)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
queue<int>q;
int num=0;
dis[s]=0;vis[s]=1;
q.push(s);
while(!q.empty())
{
if(num>6*m)
return inf;
int x=q.front();q.pop();
for(int i=fir[x];i;i=nex[i])
{
int v=tar[i];
if(dis[x]+len[i]<dis[v])
{
if(!vis[v])
q.push(v);
dis[v]=dis[x]+len[i];
vis[v]=1;
}
}
num++;vis[x]=0;
}
return dis[n];
}
6.
Prime
不要问我为什么这个算法名字那么像素数……以前以为这是
O(n2)
的,
Kruscal
比这个快多了,但是细想才发现其实好像堆优化就像
Dijkstra
一样速度会快很多(
O(n log n)
),理论上还要快一点
QAQ
,当然也不要问我空间复杂度玄学的事
void Prime()
{
priority_queue<node>q;
memset(dis,0x3f,sizeof(dis));dis[1]=0;
memset(vis,0,sizeof(vis));
q.push(node(1,0));
ans=0;
while(!q.empty())
{
int p=q.top().x;q.pop();
if(vis[p])
continue;
ans+=dis[p];
vis[p]=1;
for(int i=fir[p];i;i=nex[i])
{
int v=tar[i];
if(len[i]<dis[v])
{
dis[v]=len[i];
q.push(node(v,dis[v]));
}
}
}
}
7.
Kruscal
虽然
Prime
堆优化了之后很快啦,但是也不能否认
Kruscal
比
Prime
好写多了,也一样很快
for(int i=1;i<=m;i++)
{
int p=grand(A[i].s),q=grand(A[i].t);
if(p!=q)
{
f[p]=q;
ans+=A[i].len;
}
}
8.求树的重心
与树的直径一样,重心在求解树上的问题时可以极大地方便我们。用树的重心做树形
DP
,可以将
n2
的算法优化成
n log n
,该说是帮助我们还是恶心我们呢……
siz[i]
表示以
i
为根的子树节点个数,
void dfs(int r,int f)
{
for(int i=fir[r];i;i=nex[i])
{
int v=tar[i];
if(v!=f)
{
dfs(v,r);
wei[r]=max(wei[r],siz[v]);//子树中最大节点
siz[r]+=siz[v];
}
}
siz[r]++;
wei[r]=max(wei[r],n-siz[r]);//与除了这颗树以外的部分节点数比较
if(wei[root]>wei[r])
root=r;
}
9.求树的直径
直径在求解树上问题时很常用,因为它拥有很多神奇的性质,很多时候,你要求一些具有什么特点的路径,直径一般都是符合条件的(当然也不是绝对的)。而直径很好写,只要若干次
dfs(1,0);//以1为根dfs,最远的就是直径的一个端点r1
r1=1;r2=1;
for(int i=1;i<=n;i++)
if(dis[i]>dis[r1])
r1=i;
memset(dis,0,sizeof(dis));
dfs(r1,0);//以r1为根dfs,最远的就是另一个端点r2
for(int i=1;i<=n;i++)
if(dis[i]>dis[r2])
r2=i;
10.割点和桥
嗯,这个还是很重要的,可以找出双连通分量和边连通分量,其中边连通分量是可以缩点的,而且缩出来的是一颗树!我使用的是
tarjan
算法,速度也是
O(n)
的
割点
void dfs(int s,int f)
{
int childs=0;
dfn[s]=low[s]=++tim;//时间戳
for(int i=fir[s];i;i=nex[i])
{
int v=tar[i];
if(!dfn[v])
{
childs++;
dfs(v,i);
if(low[v]>=dfn[s])
dot[s]=1;
low[s]=min(low[s],low[v]);
}
else
if(dfn[s]>=dfn[v]&&(i^1)!=f)//这里的(i^1)!=f是不说按原路返回即可
low[s]=min(low[s],dfn[v]);
}
if(!f&&childs==1)
dot[s]=0;
}
桥
void dfs(int s,int f)
{
dfn[s]=low[s]=++tim;//时间戳
for(int i=fir[s];i;i=nex[i])
{
int v=tar[i];
if(!dfn[v])
{
dfs(v,i);
if(low[v]>dfn[s])
bridge[i]=bridge[i^1]=1;
low[s]=min(low[s],low[v]);
}
else
if(dfn[s]>=dfn[v]&&(i^1)!=f)//这里的(i^1)!=f是不说按原路返回即可
low[s]=min(low[s],dfn[v]);
}
}
11.
LCA
倍增法
倍增,首先是处理出来任意节点
i
的第
int getk(int r,int k)
{
for(int i=0;i<=20;i++)
if(k&(1<<i))
r=f[r][i];
return r;
}
int getd(int r,int d){return getk(r,dep[r]-d);}
int LCA(int a,int b)
{
if(dep[a]<dep[b])
swap(a,b);
a=getd(a,dep[b]);
if(a==b)
return a;
else
{
for(int j=20;j>=0;j--)
if(f[a][j]!=f[b][j])
a=f[a][j],b=f[b][j];
return f[a][0];
}
}
12.
网络流什么的,时间复杂度简直玄学啊(不对就是玄学),而且
NOIp
又不考(当然凡事都有个先例,说不定就考了呢……),但是网络流因为它玄学的时间复杂度,有时候还可以跑出奇迹来……
(某一道题目,最下面的一个使用二分图匈牙利匹配,上面的所有使用网络流,对比之强烈可以看出……)
有些图论题目,网络流做了还有奇效,所以相对于匈牙利匹配,网络流唯一的劣势就是代码长了……但是只要背了就不会写错,打得很快的~(不过代码真的长到怀疑人生,尽管还是没有平衡树和高精度长啦……)
int aug(int s,int augco)
{
if(s==g)
return augco;
int augc=augco,delta,mind=g;
for(int i=fir[s];i;i=nex[i])
{
int v=tar[i];
if(cap[i])
{
if(d[s]==d[v]+1)
{
delta=aug(v,min(augc,cap[i]));
cap[i]-=delta;
cap[i^1]+=delta;
augc-=delta;
if(!augc||d[w]==g)
return augco-augc;
}
mind=min(mind,d[v]+1);
}
}
if(augc==augco)
{
gd[d[s]]--;
if(gd[d[s]]==0)
d[w]=g;
d[s]=mind;
gd[d[s]]++;
}
return augco-augc;
}
int sap(int s)
{
memset(d,0,sizeof(d));
gd[0]=g;//d是距离,gd是距离汇点距离为i的点个数,g为总点数
while(d[s]<g)
flow+=aug(s,inf);
return flow;
}
Round 3
-数据结构
1.平衡树
avl+splay
灭
(m)
顶
(d)
之
(z)
灾
(z)
,最让人感到猥琐的数据结构,由于代码太长,所以我不放上来,自行脑补
2.并查集
并查集其实是一个很好用的东西,可以快速查询合并两个集合,但是如果不优化,
100%
跑挂,而更为尴尬的是有些题需要用并查集建立一棵树(如
Kruscal
树),而路径压缩会损失边的信息,按秩合并又会搞乱父子关系,这个时候就需要用普通的树存信息,并查集就主要负责查询,一样用路径压缩。
int grand(int a)
{
if(!f[a])
return a;
return f[a]=grand(f[a]);
}
int Union(int a,int b)
{
int p=grand(a),q=grand(b);
if(p!=q)
f[p]=q;
}
Round 4 -搜索
Round 5
-
DP
1.最长上升公共子序列
本来是
n4
的时间,
n4
的内存,但是一个优化就搞成了
n2
时间,
n
内存,特别巧妙,要背下来
for(int i=1;i<=n;i++)
{
int k=0;
for(int j=1;j<=m;j++)
{
if(A[i]==B[j])
f[j]=max(f[j],k+1);
if(A[i]>B[j])
k=max(k,f[j]);
}
}
3.归并排序
除了逆序对,这东西好像并没有什么用,但是还是有必要写一写
对一个区间
void m_sort(int l,int m,int r)
{
if(l==r)
return;
m_sort(l,(l+m)/2,m);
m_sort(m+1,(m+1+r)/2,r);
int i=l,j=m+1,k=l;
for(;i<=m&&j<=r;)
{
if(A[i]<=A[j])
a[k++]=A[i],i++;
else
a[k++]=A[j],j++;
}
if(j<=r)
while(j<=r)
a[k++]=A[j],j++;
if(i<=m)
while(i<=m)
a[k++]=A[i],i++;
for(i=l;i<=r;i++)
A[i]=a[i];
}
Round6
-一些鬼畜的东西
1.读入优化
这玩意儿的重要性我不想再提,要知道这是可以把一个1200ms的程序挽救到600ms的东西
void read(int &p)
{
p=0;
int f=0;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')f=1;
c=getchar();
}
while(c>='0'&&c<='9')
p=p*10+c-'0',c=getchar();
if(f)p=-p;
}
2.逆序对(顺便就搞了归并排序)
这玩意就不多说了,自己领悟
void m_sort(int l,int m,int r)
{
if(l==r)
return;
m_sort(l,(l+m)/2,m);
m_sort(m+1,(m+1+r)/2,r);
int i=l,j=m+1,k=l;
for(;i<=m&&j<=r;)
{
if(A[i]<=A[j])
a[k++]=A[i],i++;
else
{
a[k++]=A[j],j++;
sum+=m-i+1;
}
}
if(j<=r)
while(j<=r)
a[k++]=A[j],j++;
if(i<=m)
while(i<=m)
a[k++]=A[i],i++;
for(i=l;i<=r;i++)
A[i]=a[i];
}
3.叉积
我本来想写几何总版的,但是由于懒啊,所以我还是决定就写一个叉积
double cross(Vector a,Vector b){return a.x*b.y-a.y*b.x;}
4.海伦公式
这玩意好像真的用不上,不过还是写一个,不然对不起模板大纲
s=(a+b+c)/2;
S=sqrt(s*(s-a)*(s-b)*(s-c));