算法复习笔记


1. 最短路

1.1 dijstra v

#include<stdio.h>
#include<string.h>
#define inf 0x3f3f3f3f
int e[110][110],book[110];
int n,m,dis[110];
void dijstra(int a)
{
    int minn,u;
    for(int i=1; i<=n; i++){
        dis[i]=e[a][i];
        book[i]=0;
    }
    for(int i=1; i<=n-1; i++)
    {
        minn=inf;
        for(int j=1; j<=n; j++)
            if(minn>dis[j]&&book[j]==0)
            {
                minn=dis[j];
                u=j;
            }
        book[u]=1;
        for(int v=1; v<=n; v++)
            if(dis[v]>dis[u]+e[u][v])
                dis[v]=dis[u]+e[u][v];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++){
            if(i==j) e[i][j]=0;
            else e[i][j]=inf;
        }
    int t1,t2,t3;
    for(int i=1; i<=m; i++){
        scanf("%d%d%d",&t1,&t2,&t3);
        e[t1][t2]=t3;
    }
    dijstra(1);
    for(int i=1; i<=n; i++)
        printf("%d ",dis[i]);
    return 0;
}


1.2 bellman

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
#define N 1010
int n,m;
int d[N];

struct node {
    int a,b,w;
} edge[N];

int bellman(int x) {
    for(int i=1; i<=n; i++)
        d[i]=inf;
    d[x]=0;
    for(int i=1; i<n; i++)   //对每个点进行松弛
        for(int j=1; j<m; j++) { 
            if(d[edge[j].a]+edge[j].w<d[edge[j].b])
                    d[edge[j].b]=d[edge[j].a]+edge[j].w;
        }
        for(int j=1; j<=n; j++)  //还能松弛,有负边
                if(d[edge[j].a]+edge[j].w<d[edge[j].b])
                   return 0;
        return 1;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].w);
            
        int flag=bellman(1);//是否有负边权
        printf("flag=%d\n",flag);
        for(int i=1;i<=n;i++)
            printf("%d ",d[i]);
        return 0;
    }
    return 0;
}

1.3 SPFA

#include<stdio.h>
#include<string.h>
#include<queue>
#include<stack>
#include<algorithm>
using namespace std;
#define mem(x,y) memset(x,y,sizeof(x))
#define inf 0x3f3f3f3f
#define N 30010
int head[N],dis[N],vis[N];
int n,m,i,k;

struct node
{
    int v,w;
    int next;
}edge[5*N];

void add(int u,int v,int w)   //邻接表
{
    edge[k].v=v;
    edge[k].w=w;
    edge[k].next=head[u];
    head[u]=k++;
}

void SPFA() //栈优化
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        dis[i]=inf;
    stack<int> M;
    dis[1]=0;
    vis[1]=1;
    M.push(1);
    while(!M.empty())
    {
        int u=M.top();
        M.pop();
        vis[u]=0;
        for(i=head[u]; i!=-1; i=edge[i].next){
            int v=edge[i].v;  //u->v
            if(dis[v]>dis[u]+edge[i].w){
                dis[v]=dis[u]+edge[i].w;
                if(!vis[v]){   //v不在栈,入栈
                    vis[v]=1;
                    M.push(v);
                }
            }
        }
    }
}
void spfa()  //队列优化
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
       dis[i]=inf;
    queue<int> q;
    q.push(1);
    dis[1]=0; //以1开始
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].v;
            if(dis[v]>dis[u]+edge[i].w){
                dis[v]=dis[u]+edge[i].w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
}
int main()
{
    int t1,t2,t3;
    while(~scanf("%d%d",&n,&m)){
        k=0;
        memset(head,-1,sizeof(head));
        for(i=1; i<=m; i++){
            scanf("%d %d %d",&t1,&t2,&t3);
            add(t1,t2,t3);
        }
        //SPFA();  //栈优化
        spfa();
        printf("%d\n",dis[n]);
    }
    return 0;
}

2.最长公共上升子序列(长度)

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[50005],sum[50005],dp[50005][4];
int main()
{
    int i,j,n,m,t;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
             scanf("%d",&a[i]);
             sum[i]=sum[i-1]+a[i];
        }
        scanf("%d",&m);
        for(i=m;i<=n;i++)
            for(j=3;j>=1;j--)
            dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+sum[i]-sum[i-m]);
        printf("%d\n",dp[n][3]);
    }
    return 0;
}

3.1区间素数打表

区间素数打表

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define LL long long
bool prim[100010];
bool prims[100010];
void segment_sieve(LL a,LL b)
{
    for(int i=0; (LL)i*i<=b; i++) prims[i]=1;
    for(int i=0; i<=b-a; i++) prim[i]=1;
    for(int i=2; (LL)i*i<=b; i++)
    {
        if(prims[i])
        {
            for(int j=2*i; (LL)j*j<=b; j+=i)
            {
                prims[j]=0;
            }
            for(LL j=max(2LL,(a+i-1)/i)*i; j<=b; j+=i)
            {
                prim[j-a]=0;
            }
        }
    }
}
int main()
{
    LL a,b;
    int t,i,sum,cas=1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld",&a,&b);
        segment_sieve(a,b);
        sum=0;
        for(i=0; i<=b-a; i++)
        {
            if(prim[i])
                sum++;
        }
        if(a==1)sum--;  //特殊情况
        printf("Case %d: %d\n",cas++,sum);
    }
    return 0;
}

3.2埃氏打表模板

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define ll long long
#define N 1000001
int f[N],prim[N];
int k=0;
void find_prim()    
{
    k=0;
    int c;
    memset(f,0,sizeof(f));
    f[0]=f[1]=1;
    for(int i=2; i<=1000000; i++)   //此处不要用N,用具体的数  
    {
        c=i;
        if(!f[i])
        {
            prim[k++]=c;
            for(ll j=i<<1; j<=1000000; j+=i)  //这里也是,还有要把N开的大点,防止这里越界
                f[j]=1;
        }
    }
}
int main()
{
    int i,j,n,m;
    find_prim();
    for( i=0;i<=100;i++)
        printf("%d\n",prim[i]);
}

3.3欧拉打表模板

#include<stdio.h>
#include<string.h>
#define N 100000000
int p[N+5],prim[N+5];
void prime()
{
    int i,j,k=0;
    memset(p,0,sizeof(p));
    p[0]=p[1]=1;
    for(i=2;i<=N;i++)
    {
        if(!p[i])
            prim[k++]=i;
        for(j=0; j<k&&i*prim[j]<=N ;j++)
        {
            p[i*prim[j]]=1;
            if(i%prim[j]==0)
                break;
        }
    }
}
int main()
{
    prime();
    for(int i=0;i<=100;i++)
        printf("%d\n",prim[i]);
}


4.KMP v

#include<stdio.h>
#include<string.h>
char a[1010],b[1010];
int p[1010],n,m;
void pre()
{
    int j=0;
    p[1]=0;
    for(int i=1;i<m;i++){
        while(j>0&&b[j+1]!=b[i+1]) j=p[j];
        if(b[j+1]==b[i+1])j++;
        p[i+1]=j;
    }
}
void kmp()
{
    int ans=0,j=0;
    for(int i=0;i<n;i++){
        while(j>0&&a[i+1]!=b[j+1]) j=p[j];
        if(a[i+1]==b[j+1]) j++;
        if(j==m){
            //ans=i+1-m+1;    //求模板串在母串中出现的位置
            //printf("%d\n",ans);
            //j=p[j];
            ans++;        //求模板串在母串里出现的次数
            j=0;
        }
    }
    printf("%d\n",ans);
}
int main()
{
    while(~scanf("%s%s",a+1,b+1)){
       m=strlen(b+1),n=strlen(a+1);
        pre();
        kmp();
    }
    return 0;
}

KMP例题
题解:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using  namespace std;
#define N 1000000
#define M 100000
char a[N+10],b[M+10];
int p[M+10],n,m,ans;
void pre()
{
    int j=0;p[1]=0;
    for(int i=1;i<m;i++){
        while(j>0&&b[j+1]!=b[i+1]) j=p[j];
        if(b[j+1]==b[i+1]) j++;
        p[i+1]=j;
    }
}
int KMP()
{
    int j=0;ans=0;
    for(int i=0;i<n;i++){
        while(j>0&&a[i+1]!=b[j+1]) j=p[j];
        if(a[i+1]==b[j+1]) j++;
        if(j==m){
            ans++;
            j=p[j];
        }
    }
    return ans;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        memset(p,0,sizeof(p));
        scanf("%s%s",b+1,a+1);
        n=strlen(a+1);
        m=strlen(b+1);
        pre();
        printf("%d\n",KMP());
    }
    return 0;
}

5.快速幂

int quick(int a,int b,int mod)
{
    int res=1;
    while(b)
    {
        if(b&1) res=(res*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return res;
}

6.唯一分解定理

int river(ll a)   //求a的正因子数,prim数组为已经打好表的素数
{
    ll s=1,count=0,i=0;
    while(a>=prim[i]&&i<k)
    {
        count=0;
        if(a%prim[i]==0)
        {
            while(a%prim[i]==0)
            {
                a=a/prim[i];
                count++;
            }
        }
        s*=count+1;   //
        i++;
    }
    if(a>1)
        s*=1+1;   //最后剩下质因子,为一次幂。
    return s;
}

6.2.求一个数的质数因子总数

//打表
int f[N+5];
void getsum()
{
    memset(f,0,sizeof(f));
    for(i=2; i<=N; i++)//i代表因子
    {
        if(f[i]==0) //素数,同理于埃氏筛法
            for(int j=i; j<=N; j+=i)//i的倍数,从i开始,保证k*i
            {
                int k=j;
                while(k%i==0)
                {
                    p[j]++;
                    k/=i;
                }       //p[素数]一定会是1,只有它本身
            }
    }
    for(int i=2;i<=N;i++)
        p[i]=p[i]+p[i-1];
    //p[2]=p[2]+p[1]=1+0=1,p[1]=0
}

7.拓扑排序

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,m,book[maxn][maxn];
struct node
{
    int x,y;
}s[1010];
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        int l=0,k,a,b,i,j;
        memset(s,0,sizeof(s));
        memset(book,0,sizeof(book));
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            if(book[a][b]==0)//摆平重复的情况
            {
                book[a][b]=1;     //有关系的标记一下
                s[b].x++;   
            }      //记录入度
        }
        for(i=0;i<n;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(s[j].x==0)
                {
                    k=j;    //找到入度为零的点
                    break;
                }
            }
            s[l++].y=k;   //记录到数组中
            s[k].x=-1;    //入读标记为-1
            for(j=1;j<=n;j++)
            {
                if(book[k][j]==1) //与当前点相连的点
                    s[j].x--;   //入度--
            }
        }
        for(i=0;i<l;i++)
        {
            if(i!=l-1)
                printf("%d ",s[i].y);
            else
                printf("%d\n",s[i].y);
        }
    }
    return 0;
}

8.欧拉函数

const int M=5000000;
const int N=5000010;
ll p[N];
void Euler()
{
    for(int i=1;i<=M;i++)
        p[i]=i;
    for(int i=2;i<=M;i++)
    {
        if(p[i]==i)
        {
            for(int j=i;j<=M;j+=i)
                p[j]=p[j]/i*(i-1);
        }
    }
}

9.1线段树基本操作

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 400010
int inf=99999999;
using namespace std;
long long mi[N],a[N];
long long p=1,q=1;

void buildmin(int k,int l,int r)     //最小值建树
{
    if(l==r)
    {
        mi[k]=a[l];
        return;
    }
    int mid=(l+r)/2;
    buildmin(k<<1,l,mid);
    buildmin(k<<1|1,mid+1,r);
    mi[k]=min(mi[k<<1],mi[k<<1|1]);//自下而上,当前K取左右儿子较小,**该位置的条件因题而异**
}

void buildsum(int k,int l,int r)     //求和建树
{
    if(l==r)
    {
        mi[k]=a[l];
        return;
    }
    int mid=(l+r)/2;
    buildsum(k<<1,l,mid);
    buildsum(k<<1|1,mid+1,r);
    mi[k]=mi[k<<1]+mi[k<<1|1];  //变化仅此而已
}

int query_min(int k,int l,int r,int x,int y) //简单的区间询问操作
{
    if(r<x||l>y)return inf;
    if(x<=l&&r<=y)return mi[k];
    int mid=(l+r)/2;
    return min(query_min(k<<1,l,mid,x,y),query_min(k<<1|1,mid+1,r,x,y));   //果然,最小值建树区间访问求最小值比较容易....
}

void change(int k,int l,int r,int x,int v)     //点修改操作
{
    if(l>x||r<x)return;
    if(l==r&&l==x)
    {
        mi[k]=v;
        return;
    }
    int mid=(l+r)/2;
    change(k<<1,l,mid,x,v);
    change(k<<1|1,mid+1,r,x,v);
    mi[k]=min(mi[k<<1],mi[k<<1|1]);  //感觉和建树bulid类似
}

9.2区间修改与区间查询 v

void pushdown(int k,int l,int r)
{
    if(lazy[k]){
        lazy[k*2+1]=lazy[k*2]=lazy[k];
        sum[k*2]=lazy[k]*l;
        sum[k*2+1]=lazy[k]*r;
        lazy[k]=0;
    }
}
void update(int k,int l,int r,int x,int y,int v)
{
    if(x<=l&&r<=y){
        sum[k]=v*(r-l+1);
        lazy[k]=v;
        return;
    }
    int mid=(l+r)/2;
    pushdown(k,mid-l+1,r-mid);
    if(x<=mid) update(k*2,l,mid,x,y,v);
    if(mid<y) update(k*2+1,mid+1,r,x,y,v);
    sum[k]=sum[k*2]+sum[k*2+1];
}
ll query(int k,int l,int r,int x,int y)
{
    if(l>=x&&r<=y)return sum[k];
    int mid=l+r>>1;
    ll res=0;
    pushdown(k,mid-l+1,r-mid);
    if(x<=mid)res+=query(k<<1,l,mid,x,y);
    if(y>mid)res+=query(k<<1|1,mid+1,r,x,y);
    return res;
}

== 线段树例题==
题解

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 100000
#define ll long long
ll sum[4*N+10],lazy[4*N+10];
int a[N+10],n,m;
void build(int k,int l,int r)
{
    if(l==r){
        sum[k]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    sum[k]=sum[k<<1]+sum[k<<1|1];
}
void add(int k,int l,int r,int v) //k,l,r,v
{
    lazy[k]+=v;
    sum[k]+=(ll)v*(r-l+1);  //给相应区间加上一定值'+='
}
void pushdown(int k,int l,int r,int mid) //k,l,r
{
    if(lazy[k]){
        add(k<<1,l,mid,lazy[k]);
        add(k<<1|1,mid+1,r,lazy[k]);
        lazy[k]=0;
    }
}
void modify(int k,int l,int r,int x,int y,int v)
{
    if(l>=x&&r<=y) return add(k,l,r,v);  //直接更新
    int mid=(l+r)>>1;
    pushdown(k,l,r,mid);
    if(x<=mid) modify(k<<1,l,mid,x,y,v);
    if(y>mid) modify(k<<1|1,mid+1,r,x,y,v);
    sum[k]=sum[k<<1]+sum[k<<1|1];      //节点更新
}
ll query(int k,int l,int r,int x,int y)
{
    ll res=0;
    if(l>=x&&r<=y) return sum[k];   //返回值
    int mid=(l+r)>>1;
    pushdown(k,l,r,mid);
    if(x<=mid) res+=query(k<<1,l,mid,x,y); //'+='
    if(y>mid) res+=query(k<<1|1,mid+1,r,x,y); 
    return res;
}
int main()
{
    char c;
    memset(lazy,0,sizeof(lazy));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build(1,1,n);
    while(m--)
    {
        getchar();
        int x,y,z;
        scanf("%c%d%d",&c,&x,&y);
        if(c=='Q'){
            ll ans=query(1,1,n,x,y);
            printf("%lld\n",ans);
        }
        if(c=='C'){
            scanf("%d",&z);
            modify(1,1,n,x,y,z);
        }
    }
    return 0;
}

10.已知两个矩形对角点坐标,求相交部分的矩形面积以及坐标

int xx1,yy1,xx2,yy2;//相交部分坐标
long long query(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4)
{
    ll a,b;
    xx1=max(x1,x3);
    xx2=min(x2,x4);
    a=xx2-xx1;
    if(a<0) return 0;

    yy1=max(y1,y3);
    yy2=min(y2,y4);
    b=yy2-yy1;
    if(b<0) return 0;
    return a*b;//相交面积
}

11.Tarjian算法(求强联通分量)

简单讲就是用深搜所有点,先把能走到的点入栈,走遍每个点的所有出边
如果新边已经在栈中了,就更新当前点的low值。
此时,在递归返回上一层级时,在该点之前的节点也会更新其low值。
当该点无边可走,且当前dfn值与low值相同时,代表该点是一个强联通分量的根
弹出包括该节点以及该节点之前的所有点

道理其实很简单,能通过递归达到并入栈的点,代表了至少单方向是能走通的,当某一点的下一条可行边指向栈中时,则说明能构一个环,环上的任意两点都是联通的。故更新low值。
有的时候tarjian需要不止一次

/*清空栈与容器操作
for(int i=0;i<=n;i++)
         edge[i].clear();
        while(!s.empty()) s.pop();
*/
#include<stdio.h>
#include<stack>
#include<vector>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1010
vector<int>edge[N];
vector<int>a[N];//存放强联通分组元素
stack<int>s;

int low[N],dfn[N];
bool vis[N];
int k=0,l=0;  //k:d节点访问次序,即时间戳

void tarjian(int u)
{
    low[u]=dfn[u]=++k;
    s.push(u);
    vis[u]=true;
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i];
        if(!dfn[v]){
            tarjian(edge[u][i]);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])
          low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
            ++l;
        while(1){
            int x=s.top();
            s.pop();
            vis[x]=false;
            a[l].push_back(x); //可无
            if(u==x) break;
        }
    }
}
int main()
{
    memset(vis,0,sizeof(vis));
    int n,m,u,v;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
       scanf("%d%d",&u,&v);
       edge[u].push_back(v);
       //edge[a][.push_back(b) a与b相连
    }
    tarjian(1);
    for(int i=1;i<=l;i++){
        printf("Case:%d\n",i);
        for(int j=0;j<a[i].size();j++)
            printf("%d ",a[i][j]);
        printf("\n");
    }
    return 0;
}
/*
7 11
1 2
2 3
2 5
2 4
3 5
3 7
7 5
5 6
6 7
4 1
4 5
*/

12.求两个圆相交部分面积

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#define pi acos(-1.0)
using namespace std;
const double fps=1e-8;
double same(double x1,double y1,double r1,double x2,double y2,double r2)
{
    double d=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    if(r1+r2<d+fps) return 0;
    if(d<fabs(r1-r2)+fps){
        double r=min(r1,r2);
        return pi*r*r;
    }
    double x=(d*d+r1*r1-r2*r2)/(2.0*d);
    double t1=acos(x/r1);
    double t2=acos((d-x)/r2);
    return r1*r1*t1+r2*r2*t2-d*r1*sin(t1);
}

例题:VJ链接
解法:

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#define pi acos(-1.0)
using namespace std;
const double fps=1e-8;
double same(double x1,double y1,double r1,double x2,double y2,double r2)
{
    double d=sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
    if(r1+r2<d+fps) return 0;
    if(d<fabs(r1-r2)+fps){
        double r=min(r1,r2);
        return pi*r*r;
    }
    double x=(d*d+r1*r1-r2*r2)/(2.0*d);
    double t1=acos(x/r1);
    double t2=acos((d-x)/r2);
    return r1*r1*t1+r2*r2*t2-d*r1*sin(t1);
}
int main()
{
    int t,cas=1;
    double x1,x2,y1,y2,r1,r2;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lf%lf%lf%lf%lf%lf",&r1,&r2,&x1,&y1,&x2,&y2);
        printf("Case #%d: ",cas++);
        double s1=same(x1,y1,r2,x2,y2,r2);
        double s2=same(x1,y1,r2,x2,y2,r1);
        double s3=same(x1,y1,r1,x2,y2,r2);
        double s4=same(x1,y1,r1,x2,y2,r1);
        if(s4>0)
           s4=s1-s2-s3+s4;
        else
           s4=s1-s2-s3;
        printf("%.6f\n",s4);
    }
    return 0;
}

13.大数相加

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define mem(x,y) memset(x,y,sizeof(x))
#define N 1010
using namespace std;
char a[N],b[N];
int c[2*N];
void fan(char a[],int l)
{
    for(int i=0;i<l;i++) a[i]-='0';
    for(int i=0;i<l/2;i++)
        swap(a[i],a[l-1-i]);
}
int main()
{
    int t,l1,l2,L,i;
    scanf("%d",&t);
    while(t--){
        mem(a,0),mem(b,0),mem(c,0);
        scanf("%s%s",a,b);
        l1=strlen(a);
        l2=strlen(b);
        L=max(l1,l2)+1;
        fan(a,l1),fan(b,l2);
        for(i=0;i<L+1;i++){
            c[i]+=a[i]+b[i];
            if(c[i]>=10){
                c[i]-=10;
                c[i+1]=1;
            }
        }
        while(c[i]==0) i--;
        for(i;i>=0;i--)
            printf("%d",c[i]);
        printf("\n");
    }
    return 0;
}

14.树状数组(一维)

const int N=MAXN;
int c[N];
int n,v;
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y)  //第x个数加y
{
    while(x<=n){
        c[x]+=y;
        x+=lowbit(x);
    }
}
int sum(int x)    //1~x个数的和
{
    int ans=0;
    while(x){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&v);
        update(i,v);      //更新输入
    }
}

15 字典树

int ch[N][26],bo[N];  //26取决于字符种类数
int n,tot;
void insert(char *s)
{
    int u=1,l=strlen(s);
    for(int i=0; i<l; i++){
        int c=s[i]-'a';
        if(!ch[u][c])
            ch[u][c]=++tot;
        u=ch[u][c];
        bo[u]=1;
    }
}
int find(char *s)
{
    int u=1,l=strlen(s);
    for(int i=0; i<l; i++){
        int c=s[i]-'a';
        if(!ch[u][c]) return 0;
        u=ch[u][c];
    }
    return bo[u];
}

16 网络流最大流

#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
using namespace std;
#define mem(x,y) memset(x,y,sizeof(x))
#define INF 0x3f3f3f3f
int vis[20],pre[20];
int mp[20][20];
int n,m,s,e;   //star,end
queue<int>dl;

bool bfs()
{
    while(!dl.empty())dl.pop();
    mem(vis,0),mem(pre,0);
    vis[s]=1;
    dl.push(s);
    int a;
    while(!dl.empty()){
        a=dl.front();
        dl.pop();
        if(a==e)return true;
        for(int i=2; i<=n; i++){
            if(!vis[i]&&mp[a][i]){
                dl.push(i);
                vis[i]=1;
                pre[i]=a;
            }
        }
    }
    return false;
}
int maxflow()
{
    int ans=0;
    while(1){
        if(!bfs())return ans;
        int a=e,temp=INF;
        while(a!=s){
            temp=min(temp,mp[pre[a]][a]);
            a=pre[a];
        }
        a=e;
        while(a!=s){
            mp[pre[a]][a]-=temp;
            mp[a][pre[a]]+=temp;
            a=pre[a];
        }
        ans+=temp;
    }
}
int main()
{
    int t,cas=1;
    scanf("%d",&t);
    while(t--){
        mem(mp,0);
        int t1,t2,t3;
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++){
            scanf("%d%d%d",&t1,&t2,&t3);
            mp[t1][t2]+=t3;
        }
    s=1,e=n;
    printf("Case %d: %d\n",cas++,maxflow());
    }
    return 0;
}

例题:Flow Problem

17 二分图匹配

简单的二分图匹配,有男女分别nm人,每个女孩有自己的匹配意向,求最多的匹配对数
大致过程:
1 男孩 a 匹配女孩1
2 男孩 b 匹配女孩2
3 男孩 c 恰好遍历到女孩1可匹配
然后让a匹配女孩 2 (假设a和女孩2可匹配)
接着 b 匹配女孩3 (假设b和女孩3可匹配)
此时,通过调换给c腾出来一位配偶,实现了匹配数的增加
这个过程在算法中用递归实现,码量不大
例题:过山车

#include<stdio.h>
#include<string.h>
#define mem(x) memset(x,0,sizeof(x))
#define N 510
int b[N],book[N];
int dis[N][N];
int t,n,m;
bool search(int x)
{
    for(int i=1;i<=n;i++)//找每个人可以匹配人
    {
        if(dis[x][i]&&!book[i])//判段联系和在之前递归过程中i有没有被匹配
        {
            book[i]=1;//在找x点的所有地柜中,把i强制占用,即假设x连i
          if(b[i]==0||search(b[i]))//i未曾匹配过或者i的原配主可以调换配偶
          {
            b[i]=x;//可匹配
            return true;
          }
        }
    }
    return false;
}
int main()
{
    while(~scanf("%d",&t)&&t){
        scanf("%d%d",&m,&n);
        int x,y;
        mem(dis),mem(b);
        for(int i=0;i<t;i++){
            scanf("%d%d",&x,&y);
            dis[x][y]=1;
        }
        int ans=0;
        for(int i=1;i<=m;i++){
           mem(book);    //每次都需要清空
           if(search(i))ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

18 并查集

#icnlude<stdio.h>
#include<string.h>
#defien N 1010
int f[N];
int getf(int v)
{
    if(f[v]==v) return v;
    return f[v]=getf(f[v]); 
}
void merge(int x,int y)
{
    int t1=getf(x);
    int t2=getf(y);
    if(t1!=t2) f[t2]=t1;
    return;
}

19 判断线段相交

快速排斥实验,跨立实验
例题:hdu-2150

struct Node
{
	double x,y;
};
Node node[50][110];
/*快速排斥实验 跨立实验*/
double cross(Node A, Node B, Node C)  //AB.AC
{
	return(B.x-A.x)*(C.y-A.y)-(C.x-A.x)*(B.y-A.y);
}
//判段AB和CD是否相交,相交返回 真
bool intersect(Node A, Node B, Node C, Node D)
{
	if(min(A.y,B.y)<=max(C.y,D.y)&&
       min(C.y,D.y)<=max(A.y,B.y)&&
       min(A.x,B.x)<=max(C.x,D.x)&&
       min(C.x,D.x)<=max(A.x,B.x)&&
       cross(A,B,C)*cross(A,B,D)<0&&
       cross(C,D,A)*cross(C,D,B)<0)
		return true;
	return false;
}

20.数组模拟邻接表与遍历所有边

邻接表存图与遍历每条边

/**
n个点,m条边
邻接表存所有边,并遍历
模板
sample input:
4 4  (n,m)
1 4
4 3
1 2
2 4
*/
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define mem(x,y) memset(x,y,sizeof(x))
#define N 10010
int first[N],next[N];
int u[N],v[N],w[N];
int n,m;
int main()
{
    mem(first,-1);
    mem(next,-1);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u[i],&v[i]);
        
        next[i]=first[u[i]]; //核心
        first[u[i]]=i;       //代码
    }
    
    for(int i=1;i<=n;i++){
        printf("\ni=%d\n",i);

        //遍历某个点的所有边
        for(int k=first[i];k!=-1;k=next[k])
            printf("%d %d\n",u[k],v[k]);
        printf("\n");
    }
    return 0;
}

21.最小生成树

克鲁斯卡尔,加边求最小生成树
n个点,m条边,求使得n个连起来的最短长度和

/**
Kruskal
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 3000
int n,m,sum,ans;
int f[N];
struct node
{
    int a,b,c;
}e[N];
bool cmp(node x,node y)
{
    return x.c<y.c;
}
int getf(int x)
{
    if(f[x]==x) return x;
    return f[x]=getf(f[x]);
}
void merge(int x)
{
    int t1=getf(e[x].a);
    int t2=getf(e[x].b);
    if(t1!=t2){
        f[t2]=t1;      //合并树
        sum+=e[x].c;  //求最短长度和
        ans++;
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        sum=0,ans=0;   //最短长度,变数
        for(int i=1;i<=n;i++)
            f[i]=i;

        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c);
        sort(e+1,e+1+m,cmp);  //从大到小排序

        for(int i=1;i<=m;i++){    //从小边开始加入
            merge(i);   //合并边
            if(ans==n-1) break;   
        }
        printf("%d\n",sum);
    }
    return 0;
}

22 GCD

int gcd(int a,int b)
{
    if(a%b==0) return a;
    return gcd(b,a%b);
}

22.1 EX_GCD

求解二元方程的解,最小整数解,通解

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int x,y,g;
void exgcd(int a,int b) {
    if(b==0) {
        x=1,y=0,g=a;
        return;
    }
    exgcd(b,a%b);
    int t=x;          //x=y  y=x-a/b*y
    x=y;
    y=t-a/b*y;
}
int main() {
    int a,b,c,x0,y0;
    while(~scanf("%d%d%d",&a,&b,&c)) {
        exgcd(a,b);   //求解 ax+by=d 的解
        if(c%g!=0) {
            printf("无解\n");
            continue;
        }

        x0=x*c/g;  //特解:x=x0*c/g
        y0=y*c/g;        //y=y0*c/g
        printf("x0=%d y0=%d\n",x0,y0);

        int xmin,temp=b/g;
        xmin=(x0%temp+temp)%temp;  //xmin=(x%b/g+b/g)%b/g
        printf("最小整数解:%d \n",xmin);

        printf("通解:\n");
        for(int i=0; i<=15; i++) {
            int nx=x0+i*b/g;
            int ny=y0-i*a/g;
            printf("nx=%d ny=%d\n",nx,ny);
        }
    }
    return 0;
}

23 1~n约数打表

#include<vector>
using namespace std;
#define N 1010
vector<int> f[N];
void fator_form(int n)
{
    for(int i=1;i<=n;i++)
      for(int j=1;j<=n/i;j++) //1~n中以i为约数的数为i,2i,3i...(n/i)*i
        f[i*j].push_back(i);

    for(int i=1;i<=n;i++){
        printf("i=%d ",i);
        for(int j=0;j<f[i].size();j++)
           printf("%d ",f[i][j]);
        printf("\n");
    }
    return;
}

24 大数求组合数Lucas定理

给出比较大的一对数n,m,求C(n,m)%p

例题:
LibreOJ - 10228

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
ll n,m,p;
/**
大数组合数
*/
ll quick(ll a,ll b,ll p)
{
    ll res=1;
    while(b){
        if(b&1) res=(res*a)%p;
        a=a*a%p;
        b>>=1;
    }
    return res;
}

ll C(ll x,ll y,ll p)
{
    if(y==0) return 1;
    ll a=1,b=1;
    for(ll i=1;i<=y;i++){
        a=(a*(x-i+1))%p;     //n*(n-1)....(n-m+1)
        b=(b*i)%p;           //1*2*3.....m
    }                        //(a/b)%p=a%p*(b^(p-2))%p %p
    return a*quick(b,p-2,p)%p;   //费马小定理优化
}

ll Lucas(ll x,ll y,ll p)
{
    if(!y) return 1;
    return (C(x%p,y%p,p)*Lucas(x/p,y/p,p))%p;  //Lucas定理
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld%lld",&n,&m,&p);
        ll ss=Lucas(n,m,p);
        printf("%lld\n",ss);
    }
    return 0;
}

25 欧拉函数模板

1试除法

int euler(int n)
{
    int res=n;
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
            res=res/i*(i-1);
            while(n%i==0) n/=i;
        }
    }
    if(n>1) res=res/n*(n-1);
    return res;
}

2.素数筛

#define N 1000000
int e[N+10];
void euler_form()
{
     memset(m,0,sizeof(m));
    e[1]=1;
    for(int i=2;i<=N;i++)
        if(!e[i]) //i每次筛选素数
            for(int j=i;j<=N;j+=i){
                e[j]=j;
                e[j]=e[j]/i*(i-1);//i即j的素因子
            }
    return;
}

26 进制转换

十进制转R进制

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 10010
int a[1010];
int main()
{
    char c;
    int n,r;
    while(~scanf("%d%d",&n,&r)){

        int k=0,t;
        while(n){
            t=n%r;
            a[k++]=t;
            n/=r;
        }

        printf("转化为%d进制:\n",r);
        for(int i=k-1;i>=0;i--){
            if(a[i]>=10)
                printf("%c",a[i]-10+'A');
            else
                printf("%d",a[i]);
        }
        printf("\n");
        printf("\n再转换为十进制:\n");
        int old=0;
        for(int i=0;i<k;i++)
            old+=a[i]*pow(r,i);
        printf("old=%d\n",old);
    }
    return 0;
}

27 模拟数字相除(大数除以小数)

在此大于小指代数据位数的多少
要求:
输出a/b结果以及余数,(a的位数<=1000,可自拟)

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 1010   //按需改变
char a[N];
/**
大数 除以 "小"数 输出 值与余数
输入数据默认a大于b
*/
int main() {
    int k,s,b;
    while(~scanf("%s%d",a,&b)) {
        int l=strlen(a);
        for(int i=0; i<l; i++)
            a[i]-='0';
        k=0,s=0;
        while(s<b)
            s=s*10+a[k++];
        while(1) {       //模拟除法
            if(s<b) printf("0");
            else printf("%d",s/b);
            s=s%b;
            if(k==l) {
              if(s) printf("  r....%d",s);
              break;
            }
            s=s*10+a[k++];
        }
        printf("\n");
        memset(a,0,sizeof(a));
    }
    return 0;
}

28 归并排序(求逆序数对)

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define ll long long
#define N 100100
int f[N],b[N];
ll ans=0;
void mergearray(int a[],int first,int mid,int last,int temp[]) {
	int i=first,j=mid+1,k=0;
	int m=mid,n=last;

	while (i<=m&&j<=n) {
		if(a[i]<=a[j])
			temp[k++]=a[i++];
		else {
			temp[k++]=a[j++];
			//ans+=mid-i+1;       //a[i]有序,i~mid之间都是j的逆序数 
		}
	}
	while(i<=m)
		temp[k++]=a[i++];
	while(j<=n)
		temp[k++]=a[j++];
	for (i=0; i<k; i++)
		a[first+i]=temp[i];
}
void mergesort(int a[], int first, int last, int temp[]) {
	if (first<last) {
		int mid = (first + last) / 2;
		mergesort(a, first, mid, temp);//左边有序
		mergesort(a, mid + 1, last, temp); //右边有序
		mergearray(a, first, mid, last, temp); //再将二个有序数列合并
	}
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0; i<n; i++)
		scanf("%d",&f[i]);
	mergesort(f,0,n-1,b);
	//printf("%lld\n",ans);
	return 0;

}
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值