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

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

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 的值, u n1的最大奇数因子, t 则是n1所含因子 2 的个数,S是判定次数,当然我写的是 int ,正常情况下int是不会用这个算法的,所以还要写模乘法控制乘法溢出( i 不超过mod j 不超过mod ij 超过 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 堆优化了之后很快啦,但是也不能否认 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 为根的子树节点个数,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 到根的距离,而r1 r2 分别是直径的两端点。

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 的第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));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值