ACM模板(自用)

目录

  • 手动开-o2优化
  • 常用头文件
  • 读入优化
  • 并查集
  • 欧拉筛
  • 快速幂
  • 矩阵快速幂
  • 最小生成树
    • Kruskal O(nlogn)
    • Prim O((n+m)logm)
  • lcm、gcd与exgcd
  • 单源最短路之队优Dijkstra
  • 线段树
  • 二分图
  • 最长上升子序列(LIS)
  • 最长公共子序列(LCS)
  • 区间dp
  • 大数加法
  • 大数阶乘
  • kmp
  • 树状数组

手动开-o2优化

#pragma GCC optimize (2)//淦怎么会有憨批不给开-o2

常用头文件

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
/*——————————————————————————————————————————————————————————*/
#include//万能头文件 
比赛时注意是否支持万能头!

读入优化

#include
using namespace std;
#define LL long long
inline LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}

并查集

//https://www.luogu.org/problem/P3367
//题目大意:给定n个数 查询m次 1合并 2查询 判断是否在同一集合内
#include
using namespace std;
#define LL long long
inline LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,z,x,y,fa[10005];
int find(int xx)//寻找xx的根(路径压缩版) 
{
	int r=xx; 
	while(fa[r]!=r)//找到xx的根,用r记录 
	{
		r=fa[r];
	}
	int now=xx,pr;//now代表当前节点 pr代表当前节点的上一级 
	while(now!=r)//将节点x以及x到根节点中间所有的点进行路径压缩 
	{
		pr=fa[now];//记录当前节点的上一级 
		fa[now]=r;//路径压缩 
		now=pr;//将当前节点上跳为上一级,直到跳至根节点 
	}
	return r;
}
void un(int xx,int yy)//合并两个节点 
{
	int tx=find(xx);
	int ty=find(yy);
	fa[tx]=ty;
}
bool ifin(int xx,int yy)//判断两个点是否在一个集合中 
{
	return find(xx)==find(yy);
}
int main()
{
	n=read();
	m=read();
	for(register int i=1;i<=n;i++)
	{
		fa[i]=i;
	}
	for(register int i=1;i<=m;i++)
	{
		z=read();
		x=read();
		y=read();
		if(z==1)
		{
			un(x,y);
		}
		else
		{
			if(ifin(x,y))
			{
				cout<<"Y"<<endl;
			}
			else
			{
				cout<<"N"<<endl; 
			}
		}
	}
	return 0;
}

欧拉筛

注意素数定义:质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数(即0,1均非素数)

//https://www.luogu.org/problem/P3383
//题目大意 给定1-n个数 m次查询 输出该数是否是素数
#include
using namespace std;
#define LL long long
#define MOD 10000000
LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
//质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
int n,m,cnt,t,prime[10000005];//prime记录素数 
bool v[10000005];//记录是否是合数 
int main()
{
	n=read();
	m=read();
	v[0]=1;//0不是素数 
	v[1]=1;//1不是素数 
	for(register int i=2;i<=n;i++)
	{
		if(!v[i])//如果i是素数 用prime记录,prime里面的素数严格递增 
		{
			prime[++cnt]=i;
		}
		for(register int j=1;j<=cnt;j++)//遍历所有素数 
		{
			if(prime[j]*i>n)
			{
				break ;
			}
			v[prime[j]*i]=1;
			if(i%prime[j]==0)//如果该数(i)大于等于这个数的最小质因子,就跳出
			//详见 https://www.cnblogs.com/jason2003/p/9761296.html 解释 
			{
				break ;
			}
		}
	}
	for(register int i=1;i<=m;i++)
	{
		t=read();
		if(v[t])
		{
			cout<<"No"<<endl;//被筛掉了——>不是素数
		}
		else
		{
			cout<<"Yes"<<endl;//是素数
		}
	}
	return 0;
} 
/*
算法思路:

  对于每一个数(无论质数合数)x,筛掉所有小于x最小质因子的质数乘以x的数。比如对于77,它分解质因数是7*11,
  那么筛掉所有小于7的质数*77,筛掉2*77、3*77、5*77。

  好吧,是不是听起来太简单了。。。。没事,重点在证明。

算法证明:

  首先我们要明确证明思路。如果要证明它是对的,只要保证两点:没用重复筛、没有漏筛

  1、没有重复筛。

    我们假设一个合数分解成p1*p2*p3,并且保证p1p1,所以这样是筛不到的。唯一会筛的是第一种:p1和 p2*p3。

  2、没有漏筛。

  还是假设把这个合数分解质因数a*b*c,保证a

快速幂

//https://www.luogu.org/problem/P1226
#include
using namespace std;
#define LL long long
#define MOD 10000000
inline LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL b,p,k,s;
LL quickpow(LL x,LL y,LL m)//二分思想  
{
	x=x%m;//防止第一个x*x就爆掉longlong 
	LL ans=1;//记录答案 
	while(y)// 注意如果y=0的话不会进入循环 
	{
		if(y&1)//判断y是否是单数 
		{
			ans=ans*x;
			ans=ans%m;
		}
		x=x*x;
		x=x%m;
		y=y>>1;
	}
	ans=ans%m;//防止出现特殊数据:1^0%1=0的情况 
	return ans;
}
int main()
{
	b=read();
	p=read();
	k=read();
	s=quickpow(b,p,k);
	printf("%lld^%lld mod %lld=%lld\n",b,p,k,s);
	return 0;
}

矩阵快速幂

只有左矩阵的列数等于右矩阵的行数才可乘
(其实矩阵快速幂的难点在构造矩阵=.=)
矩阵乘法:行乘列(ans[i][j]=a[i][k]*b[k][j]的和)

//例题:求斐波那契数列
#include
using namespace std;
#define LL long long
#define MOD 1000000007
#define MAXN 3
LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
//只有左矩阵的列数等于右矩阵的行数才可乘
LL n;
struct Matrix
{
    LL mat[MAXN][MAXN];

};
inline void init(Matrix &x)//注意此处的&
{
    x.mat[1][1]=1;
    x.mat[1][2]=1;
    x.mat[2][1]=1;
    x.mat[2][2]=0;
}
Matrix t;
Matrix cheng(Matrix a,Matrix b,LL nt,LL mod)//nt代表几阶矩阵
{
    Matrix an;
    for(register int i=1;i<=nt;i++)//初始化
    {
        for(register int j=1;j<=nt;j++)
        {
            an.mat[i][j]=0;
        }
    }
    for(register int i=1;i<=nt;i++)
    {
        for(register int j=1;j<=nt;j++)
        {
            for(register int k=1;k<=nt;k++)
            {
                an.mat[i][j]+=a.mat[i][k]*b.mat[k][j];
                an.mat[i][j]%=mod;
            }
        }
    }
    return an;
}
Matrix quickpow(Matrix a,LL b,LL nt,LL mod)//nt 代表几阶矩阵 b代表幂数
{
    Matrix ans,res;
    for(register int i=1;i<=nt;i++)
    {
        for(register int j=1;j<=nt;j++)
        {
            if(i==j)
            {
                ans.mat[i][j]=1;
            }
            else
            {
                ans.mat[i][j]=0;
            }
            res.mat[i][j]=0;
        }
    }
    res=a;
    while(b)
    {
        if(b&1)
        {
            ans=cheng(ans,res,2,MOD);
        }
        res=cheng(res,res,2,MOD);
        b>>=1;
    }
    return ans;
}
int main()
{
    n=read();
    init(t);
    cout<<quickpow(t,n-1,2,MOD).mat[1][1]<<endl;
    //system("pause");
    return 0;
}


/*

|x1 x2|  ×   | y1 |  =  |x1*y1+x2*y2|
|x3 x4|      | y2 |     |x3*y1+x4*y2|

*/

最小生成树

prim算法适合稠密图(边多点少),kruskal算法适合稀疏图(边少点多)。

Kruskal O(nlogn)

//https://www.luogu.org/problem/P3366 
#include
using namespace std;
#define LL int
int read()
{
	int 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,cnt,tt;
struct node{
	int x,y,z;
};
node edge[200005];
void add(int a,int b,int w)
{
	edge[++cnt].x=a;
	edge[cnt].y=b;
	edge[cnt].z=w;
}
bool cmp(node a,node b)
{
	return a.z<b.z;
}
int fa[5005];
int found(int x)
{
	int pre;
	int r=x;
	while(fa[r]!=r)
	{
		r=fa[r];
	}
	while(x!=r)
	{
		pre=fa[x];
		fa[x]=r;
		x=pre;
	}
	return x;
}
bool ifin(int a,int b)
{
	return found(a)==found(b);
}
void un(int a,int b)
{
	int aa=found(a);
	int bb=found(b);
	fa[aa]=bb;
}
int ans,aa,bb,cc;
int main()
{
	n=read();
	m=read();
	for(register int i=1;i<=n;i++)//并查集初始化 
	{
		fa[i]=i;
	}
	for(register int i=1;i<=m;i++)
	{
		aa=read();
		bb=read();
		cc=read(); 
		add(aa,bb,cc);
	} 
	sort(edge+1,edge+cnt+1,cmp);//按照边从小到大排序 
	for(register int i=1;i<=cnt;i++)
	{
		if(tt==n-1)//tt记录已经在树中的边的个数 如果等于n-1说明建树完毕 
			break ;
		if(!ifin(edge[i].x,edge[i].y))//如果该边连接的两点不在同一集合中 则合并 
		{
			ans+=edge[i].z;
			un(edge[i].x,edge[i].y);
			tt++;
		}
	}
	if(tt<n-1)//判断是否成功建树 
	{
		cout<<"orz"<<endl;
	}
	else
	{
		cout<<ans<<endl;
	}
	return 0;
}

Prim O((n+m)logm)

//https://www.luogu.org/problem/P3366 
#include
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 200007
LL read()
{
    LL 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*10+ch-'0';
        ch=getchar();
    }
    return w*x;
}
int xi,yi,zi,cnt,n,m,dis[5001],mp[5001][5001];
bool v[5001];
struct nn
{
    int x,d;
    bool operator <(const nn& b)const
    {
        return d>b.d;
    }
};
priority_queue < nn > q;//优先队列优化
inline int prim(int s)
{
    int ans=0;
    v[s]=1;
    nn now;
    now.d=0;
    now.x=s;
    nn nw;
    for(register int ct=1;ct<=n-1;ct++)//找n-1个点
    {
        for(register int i=1;i<=n;i++)
        {
            nw=now;//憨批操作=.=
            if(mp[now.x][i]&&!v[i])//如果点i没有加入到树中 则将该边入队
            {
                now.d=mp[now.x][i];
                now.x=i;
                q.push(now);
            }
            now=nw;
        }
        while(!q.empty()&&v[q.top().x])//如果该点已经入树了 就跳过
        {
            q.pop();
        }
        if(q.empty())
        {
            break ;
        }
        now=q.top();
        q.pop();
        ans+=now.d;//记录树的大小
        v[now.x]=1;//标记该点已经在树中
    }
    return ans;
}
int main()
{
    n=read();
    m=read();
    for(register int i=1;i<=5000;i++)//初始化
    {
        for(register int j=1;j<=5000;j++)
        {
            if(i==j)
                mp[i][j]=0;
            mp[i][j]=MAXN;
        }
    }
    for(register int i=1;i<=m;i++)
    {
        xi=read();
        yi=read();
        zi=read();
        mp[xi][yi]=min(mp[xi][yi],zi);//防止重边
        mp[yi][xi]=min(mp[yi][xi],zi);//防止重边
    }
    cout<<prim(1)<<endl;//输出答案
    //system("pause");
    return 0;
}

lcm、gcd与exgcd

注意该无法处理含有负数的情况

#include
using namespace std;
#define LL long long
LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL a,b,X,Y;
LL gcd(LL x,LL y)//辗转相除法
{
    LL z;
	while(y!=0)
	{
		z=x;
		x=y;
		y=z%y;
	}
	return x;
}
LL exgcd(LL a,LL b,LL &x,LL &y)//用来解a*x+b*y=gcd(a,b)的情况 
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	LL r=exgcd(b,a%b,x,y);
	LL t=x;
	x=y;
	y=t-a/b*y;
	return r;
}
LL lcm(LL a,LL b)//最小公倍数数=a*b/最大公因数
{
	LL ans=a/gcd(a,b);//先计算除法防止爆掉 long long
	return ans*b;
}
int main()
{
	while(cin>>a>>b)
	{
		cout<<"LCM:"<<lcm(a,b)<<endl;
		cout<<"GCD:"<<gcd(a,b)<<endl;
		LL tt=exgcd(a,b,X,Y);
		cout<<"exGCD:"<<a<<"*"<<X<<'+'<<b<<'*'<<Y<<'='<<tt<<endl;
	}
	return 0;
 } 

单源最短路之队优Dijkstra

//https://www.luogu.org/problem/P4779 
#include
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
int n,m,s;
int ui,vi,wi;
int cnt,head[100005],d[100005];
struct node{
	int to,next,w;
};
node edge[200005];
void add(int x,int y,int w)
{
	edge[++cnt].to=y;
	edge[cnt].next=head[x];
	edge[cnt].w=w;
	head[x]=cnt;
}
struct nn{
	int x,dis;
	bool operator <(const nn& b)const//重载运算符'<' 
	{
		return dis>b.dis;//顺序按照dis从小到大排(此处与sort的cmp恰好相反) 
	}
};//记录入队的节点和距离 
priority_queue < nn > q;
inline nn init(int xx,int dd)
{
	nn t;
	t.x=xx;
	t.dis=dd;
	return t;
}
nn now;
void dij(int s)
{
	for(register int i=1;i<=n;i++)//初始化 
	{
		d[i]=INF;
	}
	d[s]=0;//d[x]代表点s到 x的距离 
	now.x=s;
	now.dis=0;
	q.push(now);
	while(!q.empty())
	{	
		now=q.top();
		q.pop();
		if(now.dis>d[now.x])//优化 如果该边权大于到点now.x的距离 说明该边比已有点更差就没有必要去遍历该点了  
			continue ;
		for(register int i=head[now.x];i!=0;i=edge[i].next)//遍历当前now点所连接的所有边 
		{
			if(d[edge[i].to]>d[now.x]+edge[i].w)//如果该边比已有点更优 则对该边所连的点进行松弛,并将该点入列 
			{
				d[edge[i].to]=d[now.x]+edge[i].w;
				q.push(init(edge[i].to,d[edge[i].to]));
			}
		}
	}
}
int main()
{
	n=read();
	m=read();
	s=read();
	for(register int i=1;i<=m;i++)
	{
		ui=read();
		vi=read();
		wi=read();
		add(ui,vi,wi);
	}
	dij(1);
	for(register int i=1;i<=n;i++)
	{
		cout<<d[i]<<' ';
	}
	return 0;
}

线段树

难点在多种操作(例如区间加与区间乘与区间覆盖等操作同时存在时down函数的处理顺序上以及对不同lazy下标优先级与操作顺序上是否进行清零等操作)
看起来好乱

#include
using namespace std;
#define LL long long
#define MAXN 0x3f3f3f3f
#define INF 500007
LL read()
{
    LL 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*10+ch-'0';
        ch=getchar();
    }
    return w*x;
}
struct node
{
    LL l,r,lazy,w;
};
node tree[INF*4];
LL n,q,f,xx,yy,ans,d;
void update(LL k)//更新k点的值
{
    tree[k].w=tree[k<<1].w+tree[k<<1|1].w;
}
void build(int x,int y,int k)//建树
{
    tree[k].l=x;
    tree[k].r=y;
    if(x==y)
    {
        tree[k].w=read();
        return ;
    }
    LL mid=(x+y)>>1;
    build(x,mid,k<<1);
    build(mid+1,y,k<<1|1);
    update(k);
}
void down(LL k)//下推lazy标记(此时该节点已经更新完成,该操作为更新两个子节点的值)
{
    tree[k<<1].lazy+=tree[k].lazy;
    tree[k<<1|1].lazy+=tree[k].lazy;
    tree[k<<1].w+=tree[k].lazy*(tree[k<<1].r-tree[k<<1].l+1);
    tree[k<<1|1].w+=tree[k].lazy*(tree[k<<1|1].r-tree[k<<1|1].l+1);
    tree[k].lazy=0;
}
void add(LL x,LL y,LL num,LL k)//区间增加 xy为查询区间 该节点区间直接用tree[k].l tree[k].r表示
{
    if(tree[k].l>=x&&tree[k].r<=y)
    {
        tree[k].w+=num*(tree[k].r-tree[k].l+1);
        tree[k].lazy+=num;
        return ;
    }
    down(k);//如果要继续二分,则要更新它的子节点,即进行下推lazy的操作
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(x<=mid)
    {
        add(x,y,num,k<<1);
    }
    if(y>mid)
    {
        add(x,y,num,k<<1|1);
    }
    update(k);//记得最后更新当前节点
}
void oneadd(LL nx,LL num,LL k)//单点增加
{
    if(tree[k].l==tree[k].r&&tree[k].l==nx)
    {
        tree[k].w+=num;
        return ;
    }
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(nx<=mid)
        oneadd(nx,num,k<<1);
    else
        oneadd(nx,num,k<<1|1);
    update(k);
}
LL getsum(LL x,LL y,LL k)//求和
{
    LL res=0;
    if(tree[k].l>=x&&tree[k].r<=y)
    {
        res=tree[k].w;
        return res;
    }
    down(k);
    LL mid=(tree[k].l+tree[k].r)>>1;
    if(x<=mid)
        res+=getsum(x,y,k<<1);
    if(y>mid)
        res+=getsum(x,y,k<<1|1);
    return res;
}
int main()
{
    n=read();
    build(1,n,1);
    q=read();
    while(q--)
    {
        f=read();
        xx=read();
        yy=read();
        if(f==1)
        {
            d=read();
            add(xx,yy,d,1);
        }
        else
        {
            ans=getsum(xx,yy,1);
            printf("%lld\n",ans);
        }
        
    }
    //system("pause");
    return 0;
}

二分图

#include
using namespace std;
#define LL long long
LL read()
{
	LL 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*10+ch-'0';
		ch=getchar();
	}
	return w*x;
}
LL u,v,n,m,e,ans,mp[2000][2000],used[2000],b[2000];
bool found(int x)
{
	for(register int i=1;i<=m;i++)
	{
		if(mp[x][i]&&!used[i])//x到i有边并且i没有被尝试改变归属且失败
		{
			used[i]=1;
			if(!b[i]||found(b[i]))//如果i没有归属或者成功地改变了i原本归属的归属
			{
				b[i]=x;//令i的归属为x
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	n=read();//集合1的个数
	m=read();//集合2的个数
	e=read();//边的条数
	for(register int i=1;i<=e;i++)
	{
		u=read();
		v=read();
		if(u>n)//原题数据有锅 无视就好
			continue ;
		if(v>m)
			continue ;
		mp[u][v]=1;//从u到v有边
	}
	for(register int i=1;i<=n;i++)
	{
		memset(used,0,sizeof(used));
		if(found(i))
			ans++;//记录匹配数量
	}
	cout<<ans<<endl;
	//system("pause");
	return 0;
}
// 判断一个图是否为二分图:将一个点标记为1 将与该点相连的点标记为2
// 将与标为2的点相连的点标记为1 如果有冲突 则不是二分图

最长上升子序列(LIS)


//初始化dp为inf=0x3f3f3f
memset(dp,inf,sizeof(dp));
 
for (int i=0;i<len;i++)
{
        //获取位置
        int pos=lower_bound(dp,dp+len,s[i])-dp;
        //更新位置
        dp[pos]=s[i];
        ans=max(ans,pos);
}
 
//答案为ans+1
cout << ans+1 << endl;

最长公共子序列(LCS)


//len1和len2为两字符串长度
for (int i=1;i<=len1;i++)
	for (int j=1;j<=len2;j++)
		//相等时直接更新
		if (s1[i-1]==s2[j-1])
			dp[i][j]=dp[i-1][j-1]+1;
		//不相等时继承之前的最大值
		else
			dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
 
//答案为dp[len1][len2]
cout << dp[len1][len2] << endl;

区间dp

//初始化DP数组
for (int i=1;i<=n;i++)
	dp[i][i]=INF(0 or 其他)
 
//枚举区间长度
for (int len=1;len<n;len++)
	//枚举起点
	for (int i=1;i+len<=n;i++)
	{
		//区间终点
		int j=i+len;
		//枚举分割点
		for (int k=i;k<j;k++)
			dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);
	}
 
//区间1-n的结果
cout << dp[1][n] << endl;

大数加法

/*
    |大数模拟加法|
    |用string模拟|
    |16/11/05ztx, thanks to caojiji|
*/

string add1(string s1, string s2)
{
    if (s1 == "" && s2 == "")   return "0";
    if (s1 == "")   return s2;
    if (s2 == "")   return s1;
    string maxx = s1, minn = s2;
    if (s1.length() < s2.length()){
        maxx = s2;
        minn = s1;
    }
    int a = maxx.length() - 1, b = minn.length() - 1;
    for (int i = b; i >= 0; --i){
        maxx[a--] += minn[i] - '0'; //  a一直在减 , 额外还要减个'0'
    }
    for (int i = maxx.length()-1; i > 0;--i){
        if (maxx[i] > '9'){
            maxx[i] -= 10;//注意这个是减10
            maxx[i - 1]++;
        }
    }
    if (maxx[0] > '9'){
        maxx[0] -= 10;
        maxx = '1' + maxx;
    }
    return maxx;
}

大数阶乘

/*
    |大数模拟阶乘|
    |用数组模拟|
    |16/12/02ztx|
*/

#include 
#include 

using namespace std;

typedef long long LL;

const int maxn = 100010;

int num[maxn], len;

/*
    在mult函数中,形参部分:len每次调用函数都会发生改变,n表示每次要乘以的数,最终返回的是结果的长度
    tip: 阶乘都是先求之前的(n-1)!来求n!
    初始化Init函数很重要,不要落下
*/

void Init() {
    len = 1;
    num[0] = 1;
}

int mult(int num[], int len, int n) {
    LL tmp = 0;
    for(LL i = 0; i < len; ++i) {
         tmp = tmp + num[i] * n;    //从最低位开始,等号左边的tmp表示当前位,右边的tmp表示进位(之前进的位)
         num[i] = tmp % 10; //  保存在对应的数组位置,即去掉进位后的一位数
         tmp = tmp / 10;    //  取整用于再次循环,与n和下一个位置的乘积相加
    }
    while(tmp) {    //  之后的进位处理
         num[len++] = tmp % 10;
         tmp = tmp / 10;
    }
    return len;
}

int main() {
    Init();
    int n;
    n = 1977; // 求的阶乘数
    for(int i = 2; i <= n; ++i) {
        len = mult(num, len, i);
    }
    for(int i = len - 1; i >= 0; --i)
        printf("%d",num[i]);    //  从最高位依次输出,数据比较多采用printf输出
    printf("\n");
    return 0;
}
  1. 01背包

1.1 题目

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i],求将哪些物品装入背包可使价值总和最大。
1.2 特点

每种物品仅有一件,可以选择放与不放。

1.3 基本的状态转移方程

f[i][j] = max(f[i − 1][j], f[i − 1][j − w[i]] + v[i])

1.4 基本模板

for(int i = 0; i < h; i++)
{
for(int j = c; j >= v[i]; j–)
{
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
}
1.5沾题

1.P2925 [USACO08DEC]干草出售Hay For Sale

简单01背包有一点优化:背包装满后就没必要再继续循环了。

#include
using namespace std;
const int maxn = 50010;

int c, h, v[maxn], dp[maxn];
int main()
{
scanf("%d%d", &c, &h);
for(int i = 0; i < h; i++) scanf("%d", &v[i]);
for(int i = 0; i < h; i++)
{

    for(int j = c; j >= v[i]; j--)
    {
        dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
        if(dp[j] == j) continue;
    }
    if(dp[c] == c) // 优化装满后退出
    {
        break;
    }
}

printf("%d\n", dp[c]);
return 0;

}
1.6 常数优化

for(int i = 0; i < h; i++)
{
int sum = 0;
for(int k = i; k < h; k++) sum += v[i];
int maxx = max(c - sum, v[i]);
for(int j = c; j >= maxx; j–)
{
dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
}
if(dp[c] == c)
{
break;
}
}
1.7初始化细节

(1)恰好装满背包,初始化dp[0] = 0,其余F[1 … V] 均设为-INF

(2)未要求将背包装满,只是价格尽量大 F[0 … V] 均设为0

2.完全背包

2.1 题目

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
2.2 基本思路

这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][j]表示前i种物品恰放入一个容量为V的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:

f[i][j] = max(f[i − 1][j − k ∗ w[i]] + k ∗ v[i]) ∣ 0 <= k ∗ w[i] <= V

二维状态转移方程

f[i][j] = max(f[i − 1][j], f[i][j − w[i]] + v[i])

2.3 模板

for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
2.4 沾个题

Piggy-Bank

#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 10010;

int t, W, w[maxn], v[maxn], dp[maxn], n;
int main()
{
scanf("%d", &t);
while(t–)
{
int w1, w2;
scanf("%d%d", &w1, &w2);
fill(dp, dp + maxn, INF);
W = w2 - w1;
scanf("%d", &n);
for(int i = 0; i < n; i++)
{
scanf("%d%d", &v[i], &w[i]);
}
dp[0] = 0;
for(int i = 0; i < n; i++)
for(int j = w[i]; j <= W; j++)
{
dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
}
if(dp[W] == INF)
{
printf(“This is impossible.\n”);
}
else
{
printf(“The minimum amount of money in the piggy-bank is %d.\n”, dp[W]);
}
}
return 0;
}
3 多重背包

3.1题目

有N种物品和一个容量为V的背包。第i种物品最多有p[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

3.2 复杂度的多重背包问题

void Zero(int w, int p)
{
for(int j = W; j >= w; j–)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Complete(int w, int p)
{
for(int j = w; j <= W; j++)
{
dp[j] = max(dp[j], dp[j - w] + p);
}
}
void Multiple(int c, int w, int p)
{
if(c * w >= W)
{
Complete(w, p);
return;
}
int k = 1;
while(k < c)
{
Zero(k * w, k * p);
c = c - k;
k = 2 * k;
}

Zero(c * w, c * p);

}
3.3可行性问题O(VN)的算法

当问题是每种有若干的物品能否他Inman给定容积的背包,只需考虑装满背包的可行性O(VN)复杂度。

基本思想:设F[i, j]表示用了前i种物品填满容量为j的背包后最多还剩几个第i种物品可用。若F[i, j] = -1表示这种状态不太可行,若可行则满足F[i , j] >=0 && F[i, j] <= Mi

memset(dp, -1, sizeof(dp));
dp[0][0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= V; j++)
{
if(dp[i - 1][j] >= 0) dp[i][j] = M[i];
else dp[i][j]= -1;
}
for(int j = 0; j <= V - C[i]; j++)
{
if(dp[i][j] > 0)
dp[i][j + C[i]] = max(dp[i][j + C[i], dp[i][j] - 1);
}
}
4 混合多种背包

4.1问题
如果将前面三个背包混合起来,也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该怎么求解呢?

4.2 01背包与完全背包的混合
考虑到在01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN) O(VN)O(VN)。

4.3再加上多重背包
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN) O(VN)O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP NOIPNOIP范围的算法的话,用多重背包中将每个这类物品分成O(log(p[i])) O(log(p[i]))O(log(p[i]))个01背包的物品的方法也已经很优了

4.4赋个多校题

AreYouBusy

题意:他由很多工作0 ,至少取一个,1 最多取一个, 2随意取,让你求在时间之内,求最大的开心值。

1.第一类,至少选一项,即必须要选,那么在开始时,对于这一组的dp的初值,应该全部赋为负无穷,这样才能保证不会出现都不选的情况。

dp[i][j]=max(dp[i][j],max(dp[i][j-w[x]]+p[x],dp[i-1][j-w[x]]+p[x]));

2.第二类,最多选一项,即要么不选,一旦选,只能是第一次选。

dp[i][j]=max(dp[i][j],dp[i-1][j-w[x]]+p[x]);

3.第三类,任意选,即不论选不选,选几个都可以。

dp[i][j]=max(dp[i][j],dp[i][j-w[x]]+p[x]);

代码

#include
using namespace std;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
int n, t, x, y, v[maxn], w[maxn], dp[maxn][maxn];

int main()
{
while(scanf("%d%d", &n, &t) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++)
{
scanf("%d%d", &x, &y);
for(int k = 1; k <= x; k++) scanf("%d%d", &v[k], &w[k]);
if(y == 0) // 至少取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = -INF;
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
{
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
}
else if(y == 1) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[k]] + w[k]);
}
}
else if(y == 2) // 至多取一个
{
for(int j = 0; j <= t; j++) dp[i][j] = dp[i - 1][j];
for(int k = 1; k <= x; k++)
{
for(int j = t; j >= v[k]; j–)
dp[i][j] = max(dp[i][j], dp[i][j - v[k]] + w[k]);
}
}
}
int ans = -1;
ans = max(ans, dp[n][t]);
printf("%d\n", ans);
}
return 0;
}
5 二维费用背包

5.1题目

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为w[i]和g[i]。两种代价可付出的最大值(两种背包容量)分别为V和T。物品的价值为v[i]。
5.2模板

for(int i = 0; i < n; i++)
{
    for(int j = v; j >= a[i]; j--)
    {
        for(int k = m; k >= b[i]; k--)
        {
            dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
        }
    }
}

5.3沾题

1.P1507 NASA的食物计划

#include
using namespace std;
const int maxn = 410;

int v, m, n, a[maxn], b[maxn], c[maxn], dp[maxn][maxn];
int main()
{
scanf("%d%d", &v, &m);
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d%d%d", &a[i], &b[i], &c[i]);
for(int i = 0; i < n; i++)
{
for(int j = v; j >= a[i]; j–)
{
for(int k = m; k >= b[i]; k–)
{
dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + c[i]);
}
}
}
printf("%d\n", dp[v][m]);
return 0;
}
2.FATE

题意:打游戏有忍耐值,需要在忍耐值之内刷够经验,最多杀n只怪。

#include
using namespace std;
const int maxn = 110;

int n, m, k, s, a[maxn], b[maxn], dp[maxn][maxn];
int main()
{
while(scanf("%d%d%d%d", &n, &m, &k, &s) != EOF)
{
memset(dp, 0, sizeof(dp));
for(int i = 0; i < k; i++) scanf("%d%d", &a[i], &b[i]);
for(int i = 0; i < k; i++)
{
for(int j = b[i]; j <= m; j++)
{
for(int x = s; x >= 1; x–)
{
dp[j][x] = max(dp[j][x], dp[j - b[i]][x - 1] + a[i]);
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= s; j++)
{
if(dp[i][s] >= n)
{
ans = m - i;
break;
}
}
if(ans != 0x3f3f3f3f) break;

    }
    if(ans == 0x3f3f3f3f) printf("-1\n");
    else printf("%d\n", ans);
}
return 0;

}
6 分组背包

有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

f[k][j]=max(f[k−1][j],f[k−1][j−c[i]]+w[i]∣物品i属于组k)

模板:

for(int i = 1; i <= n; i++)
{
for(int j = m; j >= 1; j–)
{
for(int k = 1; k <= j; k++)
{
dp[j] = max(dp[j], dp[j - k] + a[i][k]);
}
}

kmp

/*
    |kmp算法|
    |字符串匹配|
    |17/1/21ztx|
*/

void getnext(char str[maxn], int nextt[maxn]) {
    int j = 0, k = -1;
    nextt[0] = -1;
    while (j < m) {
        if (k == -1 || str[j] == str[k]) {
            j++;
            k++;
            nextt[j] = k;
        }
        else
            k = nextt[k];
    }
}

void kmp(int a[maxn], int b[maxn]) {    
    int nextt[maxm];    
    int i = 0, j = 0;    
    getnext(b, nextt);    
    while (i < n) {    
        if (j == -1 || a[i] == b[j]) { // 母串不动,子串移动    
            j++;    
            i++;    
        }    
        else {    
            // i不需要回溯了    
            // i = i - j + 1;    
            j = nextt[j];    
        }    
        if (j == m) {    
            printf("%d\n", i - m + 1); // 母串的位置减去子串的长度+1    
            return;    
        }    
    }    
    printf("-1\n");
}    

树状数组

/*
    |16/11/06ztx|
*/

#include
#include
#include
#include
#include

using namespace std;

typedef long long ll;

const int maxn = 50005;

int a[maxn];
int n;

int lowbit(const int t) {
    return t & (-t);
}

void insert(int t, int d) {
    while (t <= n){
        a[t] += d;
        t = t + lowbit(t);
    }
}

ll getSum(int t) {
    ll sum = 0;
    while (t > 0){
        sum += a[t];
        t = t - lowbit(t);
    }
    return sum;
}

int main() {
    int t, k, d;
    scanf("%d", &t);
    k= 1;
    while (t--){
        memset(a, 0, sizeof(a));
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &d);
            insert(i, d);
        }
        string str;
        printf("Case %d:\n", k++);
        while (cin >> str) {
            if (str == "End")   break;
            int x, y;
            scanf("%d %d", &x, &y);
            if (str == "Query")
                printf("%lld\n", getSum(y) - getSum(x - 1));
            else if (str == "Add")
                insert(x, y);
            else if (str == "Sub")
                insert(x, -y);
        }
    }
    return 0;
}

你可能感兴趣的:(ACM模板(自用))