NOIP之前在做什么?有没有空呢?可以来打板子吗?

N logN求最大上升子序列(LIS)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int rise[999999];
int a[1100],cnt=1;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    rise[1]=a[1];
    for(int i=2;i<=n;i++)
     if(a[i]>rise[cnt])
      rise[++cnt]=a[i];
     else
     {
        int pos=lower_bound(rise+1,rise+cnt+1,a[i])-rise;
        rise[pos]=a[i];
     }
     printf("%d",cnt);
} 

质数线性筛

用于各种需要素数的题中,很重要的工具

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 1e6+1
using namespace std;
bool vis[11000000];
int prime[1100000],cnt;
int main()
{
    vis[1]=1;

    for(int i=2;i<=N;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
        }

        for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
             break;
        }
    }
}

快速幂

更基本的工具之一

#include <cstdio>
#include <iostream>
using namespace std;
int f_pow(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1) ans*=x;
        x*=x;
        y/=2;
    }
    return ans;
}
int main()
{
    int x,y;
    scanf("%d%d",&x,&y);
    printf("%d",f_pow(x,y));
}

归并排序

可以N log N排好序,主要是可以求出逆序对的个数。
相较于树状数组的优势在于,可以把数字的问题转化成字典序排序的字符串问题,在比较的时候加上二分和哈希
而树状数组无法解决这类问题(可能行,但是我不会)

#include <cstdio>
#include <iostream>
using namespace std;
int a[999999];
int tmp[999999];
int ans=0;//逆序对个数
void sort(int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)/2;
    sort(l,mid);
    sort(mid+1,r);
    int i=l,j=mid+1;
    int now=l;
    while(i<=mid||j<=r)
    {
        bool flag;
        if(i>mid) flag=0;
        if(j>r) flag=1;
        if(i<=mid&&j<=r)
         if(a[i]<=a[j]) flag=1;//千万要注意,一定加上=号
         else flag=0;
        if(flag)
         tmp[now++]=a[i++];
        else
        {
            ans+=(mid-i+1);
            tmp[now++]=a[j++];
        }
    }
    for(int i=l;i<=r;i++)
     a[i]=tmp[i];
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]);
    sort(1,n);
    for(int i=1;i<=n;i++)
     printf("%d ",a[i]);
    printf("\n%d",ans); 
    return 0;
}

树状数组

树状数组是一种简单版的线段树,核心思想的一个lowbit的函数,不再概述。
作用主要是求逆序对的个数问题
也是 N logN
相较于归并排序 的优势在于,好打不易错
但是劣势也很明显,需要进行离散化,无法处理字典序问题

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxm=500000;
int sum[510000];//极限数据
int a[510000],w[510000];
int lowbit(int x)
{
    return x&-x;
} 
void ins(int x)
{
    for(int i=x;i<=maxm;i+=lowbit(i))
     sum[i]++; 
}
int ask(int x)
{
    int ans=0;
    for(int i=x;i>=1;i-=lowbit(i))
     ans+=sum[i];
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]),w[i]=a[i];
    sort(w+1,w+n+1);
    int t=unique(w+1,w+n+1)-w-1;//去重函数,为了搞离散化 
    for(int i=1;i<=n;i++)
     a[i]=lower_bound(w+1,w+t+1,a[i])-w;
    int ans=0;
    for(int i=n;i>=1;i--)
    {
        ans+=ask(a[i]);
        ins(a[i]+1);
    } 
    printf("%d",ans);
    return 0;
}

离散化

一些题目中的数据会有大量间隙,导致内存爆炸。
离散化用于的是对于数据的大小关系决定答案的一类问题。
比如逆序对,以及一些纸牌的问题。
具体离散化方法如下
先把所有的元素扔进一个数组中
排序,去重。
然后对于原数组,用二分找排序数组中这个数值的位置,用这个位置来做具体值。
复杂度为N log N

Tarjan

关于Tarjan的用途有很多,比如求强连通分量,求割点或者割边,以及树上最近公共祖先
对于强连通分量,我们可以有很多的用途,比方说缩点。
这里给出求强连通分量的代码

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=1e6+1;
int dfn[maxm],low[maxm],c[maxm],stk[maxm];
bool vis[maxm];
int col,num,top;
int head[maxm],net[maxm],to[maxm];
int cnt,maxi,maxc;
void add(int x,int y)
{
    cnt++;
    to[cnt]=y;
    net[cnt]=head[x];
    head[x]=cnt;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    stk[++top]=x;
    vis[x]=1;
    for(int i=head[x];i;i=net[i])
    {
        int p=to[i];
        if(!dfn[p])
        {
            tarjan(p);
            low[x]=min(low[x],low[p]);
        }
        else
        if(vis[p])
         low[x]=min(low[x],dfn[p]);
    }
    if(low[x]==dfn[x])
    {
        col++;
        //int tot=1;
        while(stk[top]!=x)
        {
            c[stk[top]]=col;
            vis[stk[top--]]=0;
        }
        //if(tot>maxi) maxi=tot,maxc=col;
        top--;
        c[x]=col;
        vis[x]=0;
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v,t;
        scanf("%d%d",&u,&v);
        add(u,v);
        //if(t==2) add(v,u);
    }
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);
    //printf("%d\n",maxi);
    for(int i=1;i<=n;i++)
     //if(c[i]==maxc)
      printf("%d ",c[i]);

     return 0;
}

Tarjan缩点

在很多题中,可以把强连通里的点当一个点处理。
这时就需要用到Tarjan的缩点法则
首先记录下原图的信息,求出原图的强连通分量,然后用原图信息重新构图即可
一道练手题

#include <cstdio>
#include <iostream>
#include <cstring> 
using namespace std;
const int maxm=2*1e6+1;
int head[maxm],net[maxm],to[maxm];
int val[maxm],cval[maxm];
int dfn[maxm],low[maxm],c[maxm],stk[maxm];
int col,num,cnt,top;
int dp[maxm];
bool vis[maxm];
struct node{
    int x,y;
}a[maxm];
void add(int x,int y)
{
    cnt++;
    to[cnt]=y;
    net[cnt]=head[x];
    head[x]=cnt;
}
void tarjan(int x)
{
    vis[x]=1;
    dfn[x]=low[x]=++num;
    stk[++top]=x;
    for(int i=head[x];i;i=net[i])
    {
        int p=to[i];
        if(!dfn[p])
        {
            tarjan(p);
            low[x]=min(low[x],low[p]);
        }
        else
         if(vis[p])
          low[x]=min(low[x],dfn[p]);
    }
    if(low[x]==dfn[x])
    {
        c[x]=++col;
        vis[x]=0;
        cval[col]+=val[x];
        while(stk[top]!=x)
        {
            vis[stk[top]]=0;
            c[stk[top]]=col;
            cval[col]+=val[stk[top]];
            vis[stk[top--]]=0;
        }
        top--;
    }
}
void dfs(int x)
{
    //if(dp[x]) return dp[x];
    dp[x]=cval[x];
    //printf("%d ",x);
    int maxi=0;
    for(int i=head[x];i;i=net[i])
    {
        int p=to[i];
        //if(!p) continue;
        if(!dp[p]) dfs(p);
        maxi=max(maxi,dp[p]);
    }
    dp[x]+=maxi;
    //return dp[x]=cval[x]+maxi;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     scanf("%d",val+i);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i].x,&a[i].y);
        add(a[i].x,a[i].y);
    }
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);

    memset(head,0,sizeof(head));
    memset(net,0,sizeof(net));
    memset(to,0,sizeof(to));
    cnt=0;

    for(int i=1;i<=m;i++)
    if(c[a[i].x]!=c[a[i].y])
     add(c[a[i].x],c[a[i].y]);
    //else printf("--%d %d--\n",a[i].x,a[i].y);
    //printf("---%d---\n",col);
    /*for(int i=1;i<=n;i++)
     printf("%d\n",c[i]);*/
    int ans=0;

    for(int i=1;i<=col;i++)
    {
        if(!dp[i]) dfs(i);
        ans=max(ans,dp[i]);
    }

    printf("%d",ans);

    return 0; 
}

LCA

LCA有非常多的用途,比方说求树上两点的最短路径长度
这条路径一定是 u->lca(u,v)->v
那么最短路径即为 dis[u]+dis[v]-2*dis[lca(u,v)]
dis为距离根节点的距离
对于LCA的求法也有很多
1:Tarjan求
2:倍增求
两种方法都很快,但是第二种方法易被卡,但对于求具体的路径却没有第二种来的方便
这里给出两种方法的代码
Tarjan求法

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=1010000;
int head[2][maxm],net[2][maxm],to[2][maxm],lca[2][maxm];
int cnt[2],fat[maxm];
bool vis[maxm];
int find(int x)
{
    if(fat[x]==x) return x;
    return fat[x]=find(fat[x]);
}
void add(int id,int x,int y)
{
    cnt[id]++;
    to[id][cnt[id]]=y;
    net[id][cnt[id]]=head[id][x];
    head[id][x]=cnt[id];
}
void tarjan(int x)
{
    fat[x]=x;
    vis[x]=1;
    for(int i=head[0][x];i;i=net[0][i])
    {
        int p=to[0][i];
        if(vis[p]) continue;
        tarjan(p);
        fat[p]=x;
    }
    for(int i=head[1][x];i;i=net[1][i])
    {
        int p=to[1][i];
        if(!vis[p]) continue;
        int fa=find(p);
        lca[1][i]=fa;
        if(i%2) lca[1][i+1]=fa;
        else lca[1][i-1]=fa;
    }
}
int main()
{
    int n,q,root;
    scanf("%d%d%d",&n,&q,&root);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(0,u,v);
        add(0,v,u);
    }
    for(int i=1;i<=q;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(1,u,v);
        add(1,v,u);
    }
    /*for(int i=1;i<=n;i++)
     if(!vis[i]) tarjan(i); */
    tarjan(root);
    for(int i=1;i<=q;i++)
     printf("%d\n",lca[1][i*2]); 
}

倍增法

#include <cstdio>
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
const int max1=1000001;
int p[max1][23],deep[max1];
int head[max1],net[2*max1],to[2*max1];
bool vis[max1];
int cnt;    
int n,q,root;
void add(int x,int y)
{
    cnt++;
    to[cnt]=y;
    net[cnt]=head[x];
    head[x]=cnt;
}
void get_deep(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=net[i])
    {
        int tmp=to[i];
        if(vis[tmp]) continue;  
        p[tmp][0]=x;
        deep[tmp]=deep[x]+1;
        get_deep(tmp); 
    }
} 
void get_fat()
{
    for(int j=1;(1<<j)<=n;j++)
     for(int i=1;i<=n;i++)
      if(p[i][j-1]!=-1)
      {
         //printf("--%d %d--\n",i,j);
         p[i][j]=p[p[i][j-1]][j-1];
         //if(i==5&&j==1) printf("--%d %d--\n",p[i][j-1],p[p[i][j-1]][j-1]);
      }

}
int lca(int u,int v)
{
    if(deep[u]<deep[v]) swap(u,v);

    int i;
    for(i=0;(1<<i)<=deep[u];i++);
    i--;
    for(int j=i;j>=0;j--)
    if(deep[u]-(1<<j)>=deep[v])
      u=p[u][j];

    if(u==v) return u;

    for(int j=i;j>=0;j--)
     if(p[u][j]!=-1&&p[u][j]!=p[v][j])
      u=p[u][j],v=p[v][j];
  return p[u][0];
}
int main()
{
    scanf("%d%d%d",&n,&q,&root);
    for(int i=1;i<n;i++)
     {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
     }
    p[root][0]=-1;
    get_deep(root);
    get_fat();
    //p[5][1]=p[p[5][0]][0];
    for(int i=1;i<=q;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }
    //p[5][1]=p[p[5][0]][0];
    //printf("%d %d",p[4][0],p[5][1]);
}

最小(最大)生成树算法

最小生成树一般有两种方法
1:Prim算法
2:Kruskal算法
第一种不加堆优化可以到N^2的复杂度
加了以后为NlogN
遗憾的是,我不会。
于是使用第二种算法
复杂度为 M logN

一般不会卡的。

扫描二维码关注公众号,回复: 288384 查看本文章

最小生成树一般用于最短连接问题,即用最小的花费把所有的点搞成联通图。
最大生成树用于限重性问题,如NOIP2013 货车运输。
这里给出最小生成树算法,最大的反过来排序就行了

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
struct node{
    int x,y;
    int cost;
};
node a[210000];
int fat[5001];
int find(int x)
{
    if(fat[x]==x) return x;
    return fat[x]=find(fat[x]);
}
bool comp(node xx,node yy)
{
    return xx.cost<yy.cost;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
     scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].cost);
    for(int i=1;i<=n;i++) fat[i]=i;
    sort(a+1,a+m+1,comp); 
    int tot=0,ans=0;
    for(int i=1;i<=m;i++)
     if(find(a[i].x)!=find(a[i].y))
     {
        fat[find(a[i].x)]=find(a[i].y);
        tot++;
        ans+=a[i].cost;
        if(tot==n-1)
        {
            printf("%d\n",ans);
            return 0;
        }
     }
     printf("orz\n");
     return 0;
}

最短路径算法

我知道的有Floyd算法,Dijkstra算法,Bellman-Ford算法,以及SPFA
其中 SPFA是Bellman-Ford的优化。
Floyd能够求任意两点之间的最短距离,而其他三种只能够求单源最短路
Floyd非常好写,复杂度N^3,如果不是太小的数据范围,基本不予考虑
Dijkstra算法无堆优化N^2,有了 N log (可是我TM不会堆优化)
SPFA是Bellman-Ford的优化,所以我这里只给出了SPFA的模板

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int max1=600001;
int head[max1],net[2*max1],to[2*max1],cost[2*max1];
bool vis[max1];
int cnt,dis[max1];
queue <int> dl;
void add(int x,int y,int z)
{
    cnt++;
    cost[cnt]=z;
    to[cnt]=y;
    net[cnt]=head[x];
    head[x]=cnt;
}
int main()
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;i++)
     {
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        add(u,v,c);
     }
    memset(dis,127/3,sizeof(dis));
    dis[s]=0;
    dl.push(s);
    vis[s]=1;
    while(!dl.empty())
    {
        int d=dl.front();
        dl.pop();
        vis[d]=0;
        for(int i=head[d];i;i=net[i])
        {
            int p=to[i];
            if(dis[p]>dis[d]+cost[i])
            {
                dis[p]=dis[d]+cost[i];
                if(vis[p]) continue;
                dl.push(p),vis[p]=1;
            }
        }
    }
    for(int i=1;i<=n;i++)
     if(dis[i]!=dis[0]) printf("%d ",dis[i]);
     else printf("2147483647 ");
    return 0;
}

并查集

普通的并查集,带反集的并查集,带权并查集,按秩合并并查集,单向并查集
前四种会,先给出第一种的基本板子,先打完最简单的板子再来填坑

#include <cstdio>
#include <iostream>
using namespace std;
const int maxm=11000;
int fat[maxm];
int find(int x)
{
    if(fat[x]==x) return x;
    return fat[x]=find(fat[x]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
     fat[i]=i;
    for(int i=1;i<=m;i++)
    {
        int opt,x,y;
        scanf("%d%d%d",&opt,&x,&y);
        if(opt==1) fat[find(x)]=find(y);
        else 
         if(find(x)==find(y)) printf("Y\n");
         else printf("N\n");
    }
    return 0;
} 

ST表

ST表其实就是倍增的思想 可以维护最大值,最小值,以及位运算的和
查询做到了O(1)

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int st[110000][19]; 
int ask(int l,int r)
{
    int len=(r-l+1);
    int t=log2(len);
    return max(st[l][t],st[r-(1<<t)+1][t]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1,x;i<=n;i++)
     scanf("%d",&x),st[i][0]=x;
    for(int j=1;(1<<j)<=n;j++)
     for(int i=1;i<=n;i++)
      if(i+(1<<j-1)<=n)
       st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",ask(l,r));
    }
    return 0;
}

线段树

我会写的只是最基本的线段树,只会维护最大值,最小值,和,单点修改,区间加与区间乘.
妈的线段树真好吃
区间 +

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
struct node{
    ll adi,sum;
    ll len;
};
node st[4*110000];
ll a[110000];
void build(int o,int l,int r)
{
    if(l==r)
    {
        st[o].sum=a[l];
        st[o].len=1;
        return;
    }
    int mid=(l+r)/2;
    build((o<<1),l,mid);
    build((o<<1)|1,mid+1,r);
    st[o].sum=st[(o<<1)].sum+st[(o<<1)|1].sum;
    st[o].len=r-l+1;
}
void push_down(int o)
{
    if(st[o].adi)
    {
        st[(o<<1)].adi+=st[o].adi;
        st[(o<<1)|1].adi+=st[o].adi;
        st[(o<<1)].sum+=st[o].adi*st[(o<<1)].len;
        st[(o<<1)|1].sum+=st[o].adi*st[(o<<1)|1].len;
        st[o].adi=0;
    }
}
void updata(int o,int l,int r,int ql,int qr,ll add)
{
    if(l>qr||r<ql) return;
    if(ql<=l&&r<=qr)
    {
        st[o].adi+=add;
        st[o].sum+=st[o].len*add;
        return;
    }
    push_down(o);
    int mid=(l+r)/2;

    updata((o<<1),l,mid,ql,qr,add);
    updata((o<<1)|1,mid+1,r,ql,qr,add);

    st[o].sum=st[(o<<1)].sum+st[(o<<1)|1].sum;
}
ll ask(int o,int l,int r,int ql,int qr)
{
    if(l>qr||r<ql) return 0;
    if(ql<=l&&r<=qr) return st[o].sum;
    push_down(o);
    int mid=(l+r)/2;
    return ask((o<<1),l,mid,ql,qr)+ask((o<<1)|1,mid+1,r,ql,qr);
}
int main()
{
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
     scanf("%lld",&a[i]);
    build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        int opt;
        scanf("%d",&opt);
        if(opt==2)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            printf("%lld\n",ask(1,1,n,l,r));
        }
        else
        {
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            updata(1,1,n,l,r,k);
        }
    }

    return 0;
}

区间 X

#include <cstdio>
#include <iostream>
#define ll long long
using namespace std;
struct node{
    ll adi,sum,adx;
    ll len;
};
node st[4*110000];
ll a[110000];
int mod;
void build(int o,int l,int r)
{
    st[o].adx=1;
    if(l==r)
    {
        st[o].sum=a[l];
        st[o].len=1;
        return;
    }
    int mid=(l+r)/2;
    build((o<<1),l,mid);
    build((o<<1)|1,mid+1,r);
    st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
    st[o].len=r-l+1;
}
inline void push_down(ll o)
{
    if(st[o].adi==0&&st[o].adx==1) return;
    st[(o<<1)].adi=(st[(o<<1)].adi*st[o].adx+st[o].adi)%mod;
    st[(o<<1)|1].adi=(st[(o<<1)|1].adi*st[o].adx+st[o].adi)%mod;
    st[(o<<1)].adx=(st[(o<<1)].adx*st[o].adx)%mod;
    st[(o<<1)|1].adx=(st[(o<<1)|1].adx*st[o].adx)%mod;
    st[(o<<1)].sum=((st[o].adi*(st[(o<<1)].len)%mod)%mod+(st[(o<<1)].sum*st[o].adx)%mod)%mod;
    st[(o<<1)|1].sum=((st[o].adi*(st[(o<<1)|1].len)%mod)%mod+(st[(o<<1)|1].sum*st[o].adx)%mod)%mod;
    st[o].adi=0;st[o].adx=1;
}
void updata_j(int o,int l,int r,int ql,int qr,ll add)
{
    if(l>qr||r<ql) return;
    if(ql<=l&&r<=qr)
    {
        st[o].adi=(st[o].adi+add)%mod;
        st[o].sum=(st[o].sum+(st[o].len*add)%mod);
        return;
    }
    push_down(o);
    int mid=(l+r)/2;

    updata_j((o<<1),l,mid,ql,qr,add);
    updata_j((o<<1)|1,mid+1,r,ql,qr,add);

    st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
}
void updata_x(int o,int l,int r,int ql,int qr,ll add)
{
    if(l>qr||r<ql) return;
    if(ql<=l&&r<=qr)
    {
        st[o].adi=(st[o].adi*add)%mod;
        st[o].sum=(st[o].sum*add)%mod;
        st[o].adx=(st[o].adx*add)%mod;
        return;
    }
    push_down(o);
    int mid=(l+r)/2;

    updata_x((o<<1),l,mid,ql,qr,add);
    updata_x((o<<1)|1,mid+1,r,ql,qr,add);

    st[o].sum=(st[(o<<1)].sum+st[(o<<1)|1].sum)%mod;
}
ll ask(int o,int l,int r,int ql,int qr)
{
    if(l>qr||r<ql) return 0;
    if(ql<=l&&r<=qr) return st[o].sum%mod;
    push_down(o);
    int mid=(l+r)/2;
    return (ask((o<<1),l,mid,ql,qr)+ask((o<<1)|1,mid+1,r,ql,qr))%mod;
}
int main()
{
    int n,q;
    scanf("%d%d%d",&n,&q,&mod);
    for(int i=1;i<=n;i++)
     scanf("%lld",&a[i]);
    build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        int opt;
        scanf("%d",&opt);
        if(opt==1)
        {
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            updata_x(1,1,n,l,r,k);
        }
        if(opt==2)
        {
            int l,r,k;
            scanf("%d%d%d",&l,&r,&k);
            updata_j(1,1,n,l,r,k);
        }
        if(opt==3)
        {
            int l,r,k;
            scanf("%d%d",&l,&r);
            printf("%lld\n",ask(1,1,n,l,r)%mod);
        }
    }

    return 0;
}

读入优化

C++中有一个函数:getchar() ,用于读入字符,那么这跟读入整数有什么关系呢?
其实,经过类似高精度的处理,就可以实现类型转换啦!
读字符肯定要比您直接读数快!

long long read()
{
    long long x=0,w=1;
    char ch=0;
    while(ch<'0'||ch>'9') 
     {
        if(ch=='-') w=-1;
        ch=getchar();
     }
    while(ch>='0'&&ch<='9')
     x=(x<<3)+(x<<1)+ch-'0',ch=getchar();//x*8+x*2,用位运算还要快!
    return x*w;
}

乘法逆元

(a+b)%p=a%p+b%p
(a*b)%p=a%p * b%p
但是
(a/b)%p !=a%p/b%p
但是若有
b*x ≡ 1 (mod p)
那么(a/b)%p=a % p * x%p
方法有三
1:费马小定理
若p为质数
那么a关于p的逆元即为 ap2ap−2
2 : 拓展欧几里得定理
p不用为质数

#include <cstdio>
using namespace std;
void e_gcd(int a,int b,int &x,int &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return;
    }
    e_gcd(b,a%b,x,y);
    int tmp=x;
    x=y;
    y=tmp-(a/b)*y;

}
int main()
{
    int a,b,x,y;
    scanf("%d%d",&a,&b);
    e_gcd(a,b,x,y);
    printf("%d",(x+b)%b);
}

3 : 线性逆元筛法

#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const int maxm=1e6+1;
int inv[3*maxm];
int main()
{
    int n,p;
    scanf("%d%d",&n,&p);

    inv[1]=1;
    for(int i=2;i<=n;i++)
    {
        inv[i]=(p-p/i)*inv[p%i];
        while(inv[i]<0) inv[i]+=p;
        printf("%d\n",inv[i-1]%p);
    }

    printf("%d\n",inv[n]%p);

    return 0;
}

差分

差分有3种 (我所知道的)
一维差分 二维差分 树上差分
1 一维差分:
我用的是用两个数组维护的差分
令 f 数组为差分数组
给 l-r区间上加数 k
只需在 f[l]+=k f[r+1]-=k
i1f[i]∑1if[i] 即为 i 位置上操作加上的数字
2: 二维差分
令s[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]。
则a[i][j]=s[1][1]…+s[1][j]+s[2][1]+…+s[2][j]+…+s[i][1]+…+s[i][j]。
对于每次以(x,y)为左上角,(x2,y2)为右下角的矩阵操作,相当于是令s[x][y]+=k,s[x][y2+1]-=k,s[x2+1][y]-=k,s[x2+1][y2+1]+=k。

s[x][y]+=k; s[x][y2+1]-=k; 
s[x2+1][y]-=k; s[x2+1][y2+1]+=k;

for (i=1; i<=n; i++)
  for (j=1; j<=n; j++)
    a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+s[i][j];

3:树上差分
对于 ui>lca(ui,vi)>viui−>lca(ui,vi)−>vi若干路径
我们想求出每条边被经过了几次
我们只需对s[ui]++,s[vi]++,s[lca(ui,vi)]=2s[ui]++,s[vi]++,s[lca(ui,vi)]−=2
然后DFS遍历 S[i]+=S[]S[i]+=∑S[子树]
这样s[i]即为边 i->fat[i]的边被经过的次数
这玩意啥用?
详见 NOIP原题运输计划

二分图匹配

匈牙利算法,可以找出最优匹配
若只判断这张图是不是二分图,可以使用黑白染色法

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
bool f[999999]; 
int match[999999];
int n,m,e;
int head[999999],to[999999],net[999999],cnt;
void add(int x,int y)
{
    cnt++;
    to[cnt]=y;
    net[cnt]=head[x];
    head[x]=cnt;
}
int dfs(int u)
{
    for(int i=head[u];i;i=net[i])
    {
      int tmp=to[i];
      if(f[tmp]) continue;
      f[tmp]=1;
      if(!match[tmp]||dfs(match[tmp]))
        {
            match[u]=tmp;
            match[tmp]=u;
            return 1;
        }

    }
    return 0;
}
int main()
{
    freopen("c.in","r",stdin);
    scanf("%d%d%d",&n,&m,&e);

    for(int i=1;i<=e;i++)
     {
        int x,y;

        scanf("%d%d",&x,&y);
        if(y>m) continue;
        add(x,y+n);
        add(y+n,x);
     }

    int sum=0; 

    for(int i=1;i<=n;i++)
     {
      for(int j=1;j<=n+m;j++)
       f[j]=0;
      sum+=dfs(i);
     }

     printf("%d",sum);

     return 0;
} 

Hash

哈希可以暴力求出两段字符串是否相同
如何得到一段区间的hash值?
hash[l->r]=hash[r]-hash[l-1]*base^len
单模容易被卡,双模大法好

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
struct node{
    ll hash1,hash2;
}a[99999];
const int mod1=1e9+7;
const int mod2=1e9+9;
const int base1=31;
const int base2=31;
char s[9999999];
ll make_hash1()
{
    ll hash=0;
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)
     hash=(hash*base1+(ll)s[i])%mod1;
    return hash;
}
ll make_hash2()
{
    ll hash=0;
    int len=strlen(s+1);
    for(int i=1;i<=len;i++)
     hash=(hash*base2+(ll)s[i])%mod2;
    return hash;
}
bool comp(node x,node y)
{
    return x.hash1<y.hash1;
}
int main()
{
    int n;
    scanf("%d\n",&n);
    for(int i=1;i<=n;i++)
     scanf("%s",(s+1)),a[i].hash1=make_hash1(),a[i].hash2=make_hash2();
    sort(a+1,a+n+1,comp);
    int ans=1;

    for(int i=2;i<=n;i++)
     if(a[i].hash1!=a[i-1].hash1||a[i].hash2!=a[i-1].hash2) ans++;

    return printf("%d",ans)*0;
}

状态压缩

状态压缩好像只与DP联系在一起
但其实并不是的,它同样运用于BFS或者是DFS中(逃)
状压的题非常好看出来
很明显的特征就是数据范围贼小。
八成就是状压或者是爆搜了
状压的基本思路就是枚举基础状态,然后转移。
这里给出一道练手题
标签是记忆化搜索,我居然用状压水过去了 (雾)
基本的状压思路是差不多的,就看你怎么操作了233

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
bool dp[1<<17][17];
char w[101],t[101],len[101];
char s[200];
int main()
{
    int n;
    scanf("%d\n",&n);
    for(int i=1;i<=n;i++)
     scanf("%s",(s+1)),w[i]=s[1],t[i]=s[strlen(s+1)],len[i]=strlen(s+1);
    for(int i=1;i<=n;i++)
     dp[1<<i-1][i]=1;
    dp[0][0]=1;
    for(int i=0;i<1<<n;i++)
     for(int j=1;j<=n;j++)
      if(dp[i][j])
      {
        for(int k=1;k<=n;k++)
         if(((1<<k-1)&i)==0&&j!=k)
          if(t[j]==w[k]) dp[i|(1<<k-1)][k]=1;
      }
    int ans=0;
    for(int i=0;i<1<<n;i++)
     for(int j=1;j<=n;j++)
     if(dp[i][j])
     {
        int num=0;
        for(int k=1;k<=n;k++)
         if(i&(1<<k-1)) num+=len[k];
        //puts("");
        ans=max(ans,num); 
     }
    printf("%d",ans);
}

高精度

压位比较快233
高精加和高精除(其他两个改就行)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int a[99999];
int b[99999];
int c[99999];
char s[99999];
int main()
{
    scanf("%s",(s+1));
    int len=strlen(s+1);
    int lena=1,k=1;
    for(int i=len;i>=1;i--)
    {
        if(k==1000) k=1,lena++;
        a[lena]+=k*(s[i]-'0');
        k*=10;
    }
    scanf("%s",(s+1));
    k=1;
    len=strlen(s+1);
    int lenb=1;
    for(int i=len;i>=1;i--)
    {
        if(k==1000) k=1,lenb++;
        b[lenb]+=k*(s[i]-'0');
        k*=10;
    }

    len=max(lena,lenb);
    for(int i=1;i<=len;i++)
    {
        c[i]+=a[i]+b[i];
        if(c[i]>=1000) c[i+1]+=c[i]/1000,c[i]%=1000;
    }
    while(c[len+1]) len++;

    printf("%d",c[len]);
    for(int i=len-1;i>=1;i--)
     printf("%03d",c[i]);
}  

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#define ll long long
using namespace std;
const ll K=1e9;
char s[1000000];
ll a[100000],b;
int main()
{
    scanf("%s",(s+1));
    int len=strlen(s+1);
    ll k=1;
    int lena=1;

    for(int i=len;i>=1;i--)
    {
        a[lena]+=(s[i]-'0')*k;
        k*=10;
        if(k==K) k=1,lena++;
    }
    ll x;
    scanf("%lld",&x);
    for(int i=lena;i>=1;i--)
    {
        a[i-1]+=a[i]%x*K;
        a[i]/=x;
    }
    while(lena>=1&&!a[lena]) lena--;

    printf("%lld",a[lena--]);
    for(int i=lena;i>=1;i--)
     printf("%09d",a[i]);
    return 0;
} 

矩阵快速幂

用于递推问题,先推出矩阵,然后模拟即可

模板题

#include <cstdio>
#include <iostream>
#include <cstring>
#define ll long long
using namespace std;
const int mod=1e9+7;
ll ans[4][4],x[4][4],c[4][4];
void ans_x()
{
    for(int i=1;i<=3;i++)
     c[1][i]=ans[1][i],ans[1][i]=0;
    for(int i=1;i<=1;i++)
     for(int j=1;j<=3;j++)
      for(int k=1;k<=3;k++)
       ans[i][j]=(ans[i][j]+(c[i][k]*x[k][j])%mod)%mod;
}
void x_x()
{
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      c[i][j]=x[i][j],x[i][j]=0;
    for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      for(int k=1;k<=3;k++)
       x[i][j]=(x[i][j]+(c[i][k]*c[k][j])%mod)%mod;
}
void init()
{
    memset(ans,0,sizeof(ans));
    memset(x,0,sizeof(x));
    ans[1][1]=1,ans[1][2]=1,ans[1][3]=1;
    x[1][1]=1,x[1][3]=1,x[2][1]=1,x[3][2]=1;
}
ll fast_pow(ll k)
{
    if(k<=3) return 1;
    init();
    k-=3;
    while(k)
    {
        if(k%2) ans_x();
        k/=2;
        x_x();
    }
    /*for(int i=1;i<=3;i++)
     for(int j=1;j<=3;j++)
      printf("-%d ",x[i][j]);*/
    return ans[1][1]%mod;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ll x;
        scanf("%lld",&x);
        printf("%lld\n",fast_pow(x));
    }

    return 0;
} 

单调队列

一个队列递增或者递减成为单调队列
单调队列对于动态求最小最大值有奇效。
详见 NOIP2016 蚯蚓
险恶的出题人,卡double ,但是没卡住Float
注意啦,维护三队列时绝对不能开FIFO队列,会RE,别问我咋知道的
蚯蚓的链接,可以当做练手题

#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll dl1[7*1000000+100000+100];
ll dl2[7*1000000+100000+100];
ll dl3[7*1000000+100000+100]; 
ll a[999999];
int main()
{
    memset(dl1,128,sizeof(dl1));
    memset(dl2,128,sizeof(dl2));
    memset(dl3,128,sizeof(dl3));
    int n,m,q,u,v,t;
    scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t);
    float p=(float)u/(float)v;
    //printf("%lf",p);
    for(int i=1;i<=n;i++)
     scanf("%d",a+i);
    sort(a+1,a+n+1);
    for(int i=n;i>=1;i--)
     dl1[n-i+1]=a[i];
    int l1=1,l2=1,l3=1;
    for(int i=1;i<=m;i++)
    {
        ll x1=-1e7,x2=-1e7,x3=-1e7;
        x1=dl1[l1];
        x2=dl2[l2];
        x3=dl3[l3];
        ll x=max(x1,max(x2,x3));
        if(x==x1) l1++;
        else 
        {
            if(x==x2) l2++;
             else l3++;
        }
        ll p1=(x+=(i-1)*q)*u/v;
        if(i%t==0) 
        printf("%lld ",x);
        //ll p1=x*p;
        ll p2=x-p1;
        p1-=(i*q);
        p2-=(i*q);
        dl2[i]=p1,dl3[i]=p2;
    }
    puts("");
    for(int i=1;i<=(n+m);i++)
    {
        ll x1=-1e7,x2=-1e7,x3=-1e7;
        x1=dl1[l1];
        x2=dl2[l2];
        x3=dl3[l3];
        ll x=max(x1,max(x2,x3));
        if(x==x1) l1++;
        else 
        {
            if(x==x2) l2++;
             else l3++;
        }
        x+=m*q;
        if(i%t==0)
        printf("%lld ",x);
    }

    return 0;
} 

Dinic模板(重置版

原来的模板写的太丑了,新写的放在这里存一下

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
const int inf=0x7fffffff;
const int maxm=1e6+100;
int head[maxm],to[maxm<<1],cap[maxm<<1],net[maxm<<1];
int cnt=1;
inline void add(int u,int v,int c){cnt++;to[cnt]=v,cap[cnt]=c,net[cnt]=head[u],head[u]=cnt;}
inline void addedge(int u,int v,int c){add(u,v,c),add(v,u,0);}
int deep[maxm];
std::queue<int> dl;
namespace Maxflow{
    inline bool BFS(int s,int t)
    {
        while(!dl.empty()) dl.pop();
        memset(deep,-1,sizeof(deep));
        dl.push(s),deep[s]=0;
        while(!dl.empty())
        {
            int now=dl.front();
            dl.pop();
            for(int i=head[now];i;i=net[i])
            if(cap[i]>0&&deep[to[i]]==-1)
             dl.push(to[i]),deep[to[i]]=deep[now]+1;
        }
        return deep[t]!=-1;
    }
    int dfs(int now,int flow,int t)
    {
        if(now==t) return flow;
        int w,used=0;
        for(int i=head[now];i;i=net[i])
        {
            int v=to[i];
            if(deep[v]==deep[now]+1&&cap[i])
            {
                w=dfs(v,std::min(cap[i],flow-used),t);
                cap[i]-=w,cap[i^1]+=w;
                used+=w;
                if(used==flow) return flow;
            }
        }
        if(!used) deep[now]=-1;
        return used;
    }
    int dinic(int s,int t)
    {
        int maxflow=0;
        while(BFS(s,t)) maxflow+=dfs(s,inf,t);
        return maxflow; 
    }
}

MCMF模板(重制版

原来的模板写的太丑了,新写的放在这里存一下

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
const int inf=0x7fffffff;
const int maxm=1e5+100;
int head[maxm],to[maxm<<1],net[maxm<<1],cost[maxm<<1],cap[maxm<<1];
int cnt=1;
inline void add(int u,int v,int c1,int c2){cnt++;to[cnt]=v,cap[cnt]=c1,cost[cnt]=c2,net[cnt]=head[u],head[u]=cnt;}
inline void addedge(int u,int v,int c1,int c2){add(u,v,c1,c2),add(v,u,0,-c2);}
namespace MCMF{
    int flow[maxm],pre[maxm],id[maxm],dis[maxm],maxflow,mincost;
    bool vis[maxm];
    std::queue<int> dl;
    inline bool SPFA(int s,int t)
    {
        while(!dl.empty()) dl.pop();
        memset(dis,127/3,sizeof(dis)),memset(pre,-1,sizeof(pre));
        dl.push(s),dis[s]=0,pre[s]=0,vis[s]=1,flow[s]=inf;
        while(!dl.empty())
        {
            int now=dl.front();
            dl.pop();
            vis[now]=0;
            for(int i=head[now];i;i=net[i])
            if(cap[i]&&dis[to[i]]>dis[now]+cost[i])
            {
                dis[to[i]]=dis[now]+cost[i];
                pre[to[i]]=now;
                id[to[i]]=i;
                flow[to[i]]=std::min(cap[i],flow[now]);
                if(!vis[to[i]]) vis[to[i]]=1,dl.push(to[i]);
            }
        }
        return pre[t]!=-1;
    }
    inline void change_cap(int s,int t,int x)
    {
        int now=t;
        while(now!=s)
        {
            cap[id[now]]-=x,cap[id[now]^1]+=x;
            now=pre[now];
        }
    }
    inline int mcmf(int s,int t)
    {
        maxflow=0,mincost=0;
        while(SPFA(s,t))
        {
            maxflow+=flow[t],mincost+=flow[t]*dis[t];
            change_cap(s,t,flow[t]);
        }
        return mincost;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_35914587/article/details/78445208
今日推荐