【复习】NOIP2017提高组-背板开始

来源:互联网 发布:java easyui 编辑:程序博客网 时间:2024/06/04 01:29

emmmmNOIP2017就要来了,还是思考一下怎么复习吧~
打模板或许是一个很不错的选择~
好了我们就响应号召,努力打模板吧~做一个优秀的背板先生(划掉)~

Round 1-数学
1.质因数分解
虽然水,但还是很有用的~num[i]表示第i个质因数,sum[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.线性筛质数
很有用的玩意儿,可以很快地刷出所有质数,有些题很好用,P[i]表示第i个质数,H[i]表示第i个数是否是质数

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.线性筛欧拉函数
这个好像不是很常用,理论上也不是O(n)(虽然很接近啦),但是还是要写……phi[i]表示第i个数的欧拉函数值

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.质数判定-MillerRabin
超牛逼的算法,可惜并不完美,所以要多测几次……pow(i,j,k)表示ij % k的值,un1的最大奇数因子,t则是n1所含因子2的个数,S是判定次数,当然我写的是int,正常情况下int是不会用这个算法的,所以还要写模乘法控制乘法溢出(i不超过modj不超过modij超过LongLongMax的时候……)

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.GCD+LCM+EXGCD
卧槽这个真的不讲
.
.
.
.
.
看什么看,真的不讲……
10.中国剩余定理
很牛,但是一般不会考裸题,所以有时是一个取余合数时坑你的玩意儿
还记得某大佬的出的神题(简单的组合数学DP,但是有除法)
某一道涉及除法的取余合数的题目
其中D代表模线性方程组中的模数(两两互质),R代表余数,tot是所有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.卡特兰数列
不是很常用,其值就等于C(2n,n)h[i]表示第i个卡特兰数

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.康托展开式
8数码问题神器(但是当变成15数码后就鸡肋了),还是很有必要写
fac[i]表示i!,而A表示排列的元素,sum表示比自己小的排列个数

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,n1]n意义下的逆元(当然如果其中有不与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=aa/bb即可

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.Floyd
虽然很纠结英文单词写对没有,但还是就这样吧……最好写的最短路,所以一般不考(尴尬),但是相较于其他单源点算法,能够实现的功能也多了很多(例如找出负环,多源点最短路)

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堆优化了之后很快啦,但是也不能否认KruscalPrime好写多了,也一样很快

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为根的子树节点个数,wei[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即可,以下代码中dis[i]表示i到根的距离,而r1r2分别是直径的两端点。

dfs(1,0);//以1为根dfs,最远的就是直径的一个端点r1r1=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,最远的就是另一个端点r2for(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的第2i个父亲,然后求第k个父亲就拆分成二进制。log n的时间一步一步跳,可以说是很快了,而且很好理解,是求LCA三大主流方法之一(还有链剖,tarjan),甚至还可以做各种查询(如往上k个父亲中的某权值的最小值)

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.sap网络流
网络流什么的,时间复杂度简直玄学啊(不对就是玄学),而且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.归并排序
除了逆序对,这东西好像并没有什么用,但是还是有必要写一写
对一个区间[l,r]排序,就把他分成两段[l,m][m,r],分别排序,再合并

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));
原创粉丝点击