模板
素数判断
int Isprime(int n)
{
for(int i=2;i*i<=n;i++)
if(n%i==0) return 0;
return 1;
}
素数打表
求n以内的素数,如果 i 不为素数就将a[ i ]设为1,否则就设为0。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[(int)1e8+5];
void prime(int n)
{
memset(a,0,sizeof(a));
for(int i=2;i<=n;i++)
for(int j=2;j*i<=n;j++)
if(!a[i*j]) a[i*j]=!a[i*j];
for(int i=2;i<=n;i++)
if(!a[i]) printf("%-5d",i);
}
int main()
{
int n;
while(~scanf("%d",&n))
prime(n);
return 0;
}
快速幂
typedef long long LL; // 视数据大小的情况而定
LL powerMod(LL x, LL n, LL m) x的n次方,结果模m
{
LL res = 1;
while (n > 0){
if (n & 1) // 判断是否为奇数,若是则true
res = (res * x) % m;
x = (x * x) % m;
n >>= 1; // 相当于n /= 2;
}
return res;
}
最大公约数___欧几里得算法(GCD)
#include<cstdio>
int gcd(int a,int b) // 返回a , b的最大公约数
{
return !b?a:gcd(b,a%b);
}
int main()
{
int a,b;
while(scanf("%d %d",&a,&b)!=EOF)
printf("%d\n",gcd(a,b));
return 0;
}
大数加法
/*
大数加法采用的是模拟的思想,就是利用数组来储存一个数的每一位
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000;
char s1[MAXN],s2[MAXN];
int n1[MAXN],n2[MAXN],sum[MAXN];
int main()
{
int T;
scanf("%d",&T);
for(int k=1;k<=T;k++)
{
scanf("%s %s",s1,s2);//用字符串来储存两个大数
memset(n1,0,sizeof(n1));//初始化数组,让它们全为0
memset(n2,0,sizeof(n2));
memset(sum,0,sizeof(sum));//初始化保存结果的数组
int len1=strlen(s1);//第一个数的长度
int len2=strlen(s2);//第二个数的长度
int j=0;
for(int i=len1-1;i>=0;i--)//将第一个数的每一位都逆序赋值给第一个数组
n1[j++]=s1[i]-'0';
j=0;
for(int i=len2-1;i>=0;i--)
n2[j++]=s2[i]-'0';
int len=len1>len2?len1:len2;//找出来两个数中比较长的那个数
int pre=0;//用来保存进位
for(int i=0;i<len;i++)//给sum赋值,要记得sum可能是大于9的,输出的时候要对10取余
{
sum[i]=n1[i]+n2[i]+pre/10;
pre=sum[i];
}
if(pre>9)//保存最高位的是sum[len-1] ,如果大于9 ,结果的位数要加一
{
sum[len]=pre/10;//取高位
len++;//位数 +1
}
int t=len;
for(int i=len-1;i>=0;i--) //去掉前置0
{
if(sum[i]==0) t--;//像 0001 + 3 这种,结果应该输出 4 而不是0004
else break;
}
printf("Case %d:\n%s + %s = ",k,s1,s2);
for(int i=t-1;i>=0;i--)
printf("%d",sum[i]%10);
printf("\n");
if(k!=T) printf("\n");//最后一组数据不要空行
}
return 0;
}
大数乘法
/*
大数乘法:
运用模拟,两个数组来储存两个大数
另外一个数组来保存运算结果
em....
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000;
char s1[MAXN],s2[MAXN];
int a[MAXN],b[MAXN],c[MAXN];
int main()
{
while(scanf("%s %s",s1,s2)!=EOF)
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
int len1=strlen(s1);//第一个数的长度
int len2=strlen(s2);//第二个数的长度
int i,j;
for(i=len1-1,j=0;i>=0;i--) a[j++]=s1[i]-'0';//用数组逆序保存第一个数
for(i=len2-1,j=0;i>=0;i--) b[j++]=s2[i]-'0';//用数组逆序保存第二个数
for(i=0;i<len1;i++)
for(j=0;j<len2;j++)
c[j+i]+=a[i]*b[j];//一定要 + *
int len=len1+len2;
for(i=0;i<len;i++)//进行进位运算
if(c[i]>9)
{
c[i+1]+=c[i]/10;
c[i]%=10;
}
int t=len;
for(i=len-1;i>=0;i--)//去掉前置0
if(c[i]==0) t--;
else break;
for(i=t-1;i>=0;i--)//输出
printf("%d",c[i]);
printf("\n");
}
return 0;
}
小根堆
#include<cstring>
template<typename item>
class smallest_heap{
private:
item heap[10001];
int len;
public:
smallest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
smallest_heap<item>::smallest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void smallest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]<heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void smallest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]>heap[son+1]) son++;
if(heap[father]>heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item smallest_heap<item>::top(){
return heap[1];
}
template<typename item>
int smallest_heap<item>::size(){
return len;
}
template<typename item>
bool smallest_heap<item>::empty(){
return len;
}
大根堆
#include<cstring>
template<typename item>
class largest_heap{
private:
item heap[10001];
int len;
public:
largest_heap();
void push(item const &);
void pop();
item top();
int size();
bool empty();
};
template<typename item>
largest_heap<item>::largest_heap(){
len=0;
memset(heap,0,sizeof(heap));
}
template<typename item>
void largest_heap<item>::push(item const &n){
heap[++len]=n;
int son=len,father=son/2;
while(heap[son]>heap[father] && father>=1){
swap(heap[son],heap[father]);
son=father,father=son/2;
}
}
template<typename item>
void largest_heap<item>::pop(){
swap(heap[1],heap[len]);
heap[len--]=0;
int father=1,son=2;
while(son<=len){
if(son<len && heap[son]<heap[son+1]) son++;
if(heap[father]<heap[son]){
swap(heap[father],heap[son]);
father=son,son=father*2;
}else break;
}
}
template<typename item>
item largest_heap<item>::top(){
return heap[1];
}
template<typename item>
int largest_heap<item>::size(){
return len;
}
template<typename item>
bool largest_heap<item>::empty(){
return len;
}
同时也可以支持自己编写的类,但须提供“<”或“>”的运算符重载,例如
class T{
private:
int a;
public:
bool operator<(T const &type){
return a<type.a;
}
};
smallest_heap<T> heap;
桶排序
例题:P1090 合并果子 - 洛谷
时间复杂度:O(n)
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int k,x,num,n1,n2,a1[30001],a2[30001],t[20001],w,sum;
int main()
{
scanf("%d",&num);
memset(a1,127/3,sizeof(a1));
memset(a2,127/3,sizeof(a2));
for (int i=1;i<=num;i++)
{
scanf("%d",&x);
t[x]++;//桶
}
for (int i=1;i<=20000;i++)
{
while (t[i])//桶排序
{
t[i]--;
a1[++n1]=i;
}
}
int i=1,j=1;
k=1;
while (k<num)
{
if (a1[i]<a2[j])//取最小值
{
w=a1[i];
i++;
}
else
{
w=a2[j];
j++;
}
if (a1[i]<a2[j])//取第二次
{
w+=a1[i];
i++;
}
else
{
w+=a2[j];
j++;
}
a2[++n2]=w;//加入第二个队列
k++;//计算合并次数
sum+=w;//计算价值
}
printf("%d",sum);
}
- 归并排序+逆序对
#include <iostream>
using namespace std;
int count = 0;//记录逆序对的个数
// 合并数组,排好序,然后在拷贝到原来的数组array
void MergeArray(int array[], int start, int end ,int mid, int temp[]) {
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end ) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
}else {
temp[k++] = array[j++];
count += mid - i + 1;
}
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= end) {
temp[k++] = array[j++];
}
for (int i = 0; i < k; i ++) {
array[start + i] = temp[i];
}
}
// 归并排序,将数组前半部分后半部分分成最小单元,然后在合并
void MergeSort(int array[], int start, int end, int temp[]) {
if(start < end) {
int mid = (start + end)/ 2;
MergeSort(array, start, mid, temp);
MergeSort(array, mid + 1, end, temp);
MergeArray(array, start, end, mid, temp);
}
}
// 在这里创建临时数组,节省内存开销,因为以后的temp都是在递归李使用的。
void MergeSort(int array[], int len) {
int start = 0;
int end = len - 1;
int *temp = new int[len];
MergeSort(array, start, end, temp);
}
void PrintArray(int array[], int len) {
for (int i = 0 ; i < len; ++i) {
cout << array[i] << " " ;
}
cout << endl;
}
int main(int argc, const char * argv[]) {
int array[] = {3,5,3,6,7,3,7,8,1};
MergeSort(array, 9);
PrintArray(array, 9);
return 0;
}
动态规划
最长公共子序列(长度+打印路径)
算法导论p.223
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1009;
char a[maxn],b[maxn];
int path[maxn][maxn],dp[maxn][maxn];//path 记录路径
void lcs(int i,int j)//打印路径
{
if(i==0||j==0) return ;//结束标志,a或者b只要有一个找完了,就不在找了
if(path[i][j]==1)//path是1的时候输出这个字符
{
lcs(i-1,j-1);//因为是从后往前找的
printf("%c",a[i-1]);//所以这句得写到递归函数下边
}
else if(path[i][j]==2)
lcs(i-1,j);
else
lcs(i,j-1);
return ;
}
int main()
{
while(~scanf("%s %s",a,b))
{
memset(dp,0,sizeof(dp));
int m=strlen(a);
int n=strlen(b);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
if(a[i-1]==b[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
path[i][j]=1;
}
else if(dp[i-1][j]>dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
path[i][j]=2;
}
else
{
dp[i][j]=dp[i][j-1];
path[i][j]=3;
}
lcs(m,n);
printf("\n");
// printf("\n%d\n",dp[m][n]);//输出最长子序列的长度
}
return 0;
最长递增子序列(长度+打印路径)
- 问题描述:
设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。 - 第一种算法:转化为LCS问题求解
设序列X=<b1,b2,…,bn>是对序列L=<a1,a2,…,an>按递增排好序的序列。那么显然X与L的最长公共子序列即为L的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。
最长公共子序列问题用动态规划的算法可解。设Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它们分别为L和X的子序列。令C[i,j]为Li与Xj的最长公共子序列的长度。
这可以用时间复杂度为O(n2)的算法求解,由于这个算法上课时讲过,所以具体代码在此略去。求最长递增子序列的算法时间复杂度由排序所用的O(nlogn)的时间加上求LCS的O(n2)的时间,算法的最坏时间复杂度为O(nlogn)+O(n2)=O(n2)。 - 第二种算法:动态规划
设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:
这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。
只求长度的(java):
public void lis(float[] L)
{
int n = L.length;
int[] f = new int[n];//用于存放f(i)值;
f[0]=1;//以第a1为末元素的最长递增子序列长度为1;
for(int i = 1;i<n;i++)//循环n-1次
{
f[i]=1;//f[i]的最小值为1;
for(int j=0;j<i;j++)//循环i 次
{
if(L[j]<L[i]&&f[j]>f[i]-1)
f[i]=f[j]+1;//更新f[i]的值。
}
}
System.out.println(f[n-1]);
}
求长度并打印的:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int a[MAXN];
int dp[MAXN];
int n;
int vis[MAXN];
void dfs(int pos)//打印路径
{
if(pos==-1) return ;
dfs(vis[pos]);
printf(" %d",pos+1);//这里是正序输出编号(从1开始的)
}
int main()
{
while(~scanf("%d",&n)&&n)
{
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
memset(vis,-1,sizeof(vis));
int res=0;
int pos=-1;
for(int i=0;i<n;i++)
{
dp[i]=1;
for(int j=0;j<i;j++)
if(a[i]>a[j])
{
if(dp[i]<dp[j]+1)
{
dp[i]=dp[j]+1;
vis[i]=j;//vis[i]=j 表示以a[i]为结尾的LIS的上一个元素是a[j]
}
}
if(res<dp[i])
{
res=dp[i];
pos=i;//找到LIS的最后一个结点
}
}
printf("The number is %d:",res);//输出LIS的长度
dfs(pos);
printf("\n");
}
return 0;
}
图论
最小生成树
Kruskal
Kruskal基本算法:每次选取最短的边,看该边相连的两点是否在同一集合内,若在,则跳过,若不在,就把两个点合并,判断与合并都用并查集实现。
Kruskal的复杂度是O(ElogE),适合稀疏图。
/*
Kruskal算法求MST
*/
#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<fstream>
using namespace std;
const int MAXN=505;//最大点数
const int MAXM=250005;//最大边数
int F[MAXN];//并查集使用 查找使用的,可以改为用set来记录在A中的结点(相关概念见算法导论)
struct Edge
{
int u,v,w;
}edge[MAXM];//储存边的信息,包括起点/终点/权值
int tol;//边数,加边前赋值为0
void addedge(int u,int v,int w)
{
edge[tol].u=u;
edge[tol].v=v;
edge[tol++].w=w;
}
bool cmp(Edge a,Edge b)//排序函数,边按照权值从小到大排序
{
return a.w<b.w;
}
int Find(int x)
{
if(F[x]==-1)
return x;
else
return F[x]=Find(F[x]);//平均时间复杂度为常数级
}
int Kruskal(int n)//传入点数,返回最小生成树的权值,如果不连通返回-1
{
memset(F,-1,sizeof(F));
sort(edge,edge+tol,cmp);
int cnt=0;//计算加入的边数
int ans=0;
for(int i=0;i<tol;i++)
{
int u=edge[i].u;
int v=edge[i].v;
int w=edge[i].w;
int t1=Find(u);
int t2=Find(v);
if(t1!=t2)
{
ans+=w;
F[t1]=t2;
cnt++;
}
if(cnt==n-1)//当添加的边数已经为n-1(n为结点数)时表示最小生成树已形成
break;
}
if(cnt<n-1)
return -1;//不连通
else
return ans;
}
int main( )
{
//freopen("in.txt","r",stdin);
int T;
cin>>T;
int n;
int c;
while(T--)
{
cin>>n;
tol=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>c;
addedge(i,j,c);
}
}
cout<<Kruskal(n)<<endl;
}
return 0;
}
prim算法
- 例题
省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。
Input
测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。
Output
对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。
Sample Input
3 3
1 2 1
1 3 2
2 3 4
1 3
2 3 2
0 100
Sample Output
3
?
#include<stdio.h>
#include<string.h>
#define INF 0x3f3f3f
int lowcost[110];//此数组用来记录当前最小生成树的子树到达其他结点的最小花费
int map[110][110];//用来记录第i个节点到其余n-1个节点的距离
int visit[110];//用来记录最小生成树中的节点
int city;
void prime()
{
int min,i,j,next,mincost=0;
memset(visit,0,sizeof(visit));//给最小生成树数组清零
for(i=1;i<=city;i++)
{
lowcost[i]=map[1][i];//初始化lowcost数组为第1个节点到剩下所有节点的距离
}
visit[1]=1;//选择第一个点为最小生成树的起点
for(i=1;i<city;i++)
{
min=INF;
for(j=1;j<=city;j++)
{
if(!visit[j]&&min>lowcost[j])//如果第j个点不是最小生成树中的点并且其花费小于min
{
min=lowcost[j];
next=j;//记录下此时最小的位置节点
}
}
if(min==INF)
{
printf("?\n");
return ;
}
mincost+=min;//将最小生成树中所有权值相加
visit[next]=1;//next点加入最小生成树
for(j=1;j<=city;j++)
{
if(!visit[j]&&lowcost[j]>map[next][j])//如果第j点不是最小生成树中的点并且此点处权值大于第next点到j点的权值
{
lowcost[j]=map[next][j]; //更新lowcost数组
}
}
}
printf("%d\n",mincost);
}
int main()
{
int road;
int j,i,x,y,c;
while(scanf("%d%d",&road,&city)&&road!=0)
{
memset(map,INF,sizeof(map));//初始化数组map为无穷大
while(road--)
{
scanf("%d%d%d",&x,&y,&c);
map[x][y]=map[y][x]=c;//城市x到y的花费==城市y到想的花费
}
prime();
}
return 0;
}
单源最短路径算法
Dijkstra算法
1.定义概览
Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。注意该算法要求图中不存在负权边。
问题描述:在无向图 G=(V,E) 中,假设每条边 E[i] 的长度为 w[i],找到由顶点 V0 到其余各点的最短路径。(单源最短路径)
2.算法描述
1)算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集合S中,直到全部顶点都加入到S中,算法就结束了),第二组为其余未确定最短路径的顶点集合(用U表示),按最短路径长度的递增次序依次把第二组的顶点加入S中。在加入的过程中,总保持从源点v到S中各顶点的最短路径长度不大于从源点v到U中任何顶点的最短路径长度。此外,每个顶点对应一个距离,S中的顶点的距离就是从v到此顶点的最短路径长度,U中的顶点的距离,是从v到此顶点只包括S中的顶点为中间顶点的当前最短路径长度。
2)算法步骤:
a.初始时,S只包含源点,即S={v},v的距离为0。U包含除v外的其他顶点,即:U={其余顶点},若v与U中顶点u有边,则<u,v>正常有权值,若u不是v的出边邻接点,则<u,v>权值为∞。
b.从U中选取一个距离v最小的顶点k,把k,加入S中(该选定的距离就是v到k的最短路径长度)。
c.以k为新考虑的中间点,修改U中各顶点的距离;若从源点v到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值的顶点k的距离加上边上的权。
d.重复步骤b和c直到所有顶点都包含在S中。
模板:
const int INF=0x3f3f3f3f;
const int maxn=1200;
int dist[maxn],g[maxn][maxn],N;// dist[i]表示源结点到i的最小距离,g[i][j]表示图中结点i到j的路径代价
bool vis[maxn];
void dijkstra()
{
for(int i=1;i<=N;i++)
dist[i]=(i==1)?0:INF;// 初始化,以i = 1 作为源结点
memset(vis,0,sizeof(vis));
for(int i=1;i<=N;i++)
{
int mark=-1,mindis=INF;
for(int j=1;j<=N;j++)
{
if(!vis[j]&&dist[j]<mindis)
{
mindis=dist[j];
mark=j;
}
}
vis[mark]=1;
for(int j=1;j<=N;j++)
{
if(!vis[j])
{
dist[j]=min(dist[j],dist[mark]+g[mark][j]);
}
}
}
}
内存优化后的Dijkstra:
int dist[N], point[N], n, m;
bool vis[N];
std::vector<pair<int, int> > g[N];//g[i][j] = <fi, se> 为边(i , fi)的距离se;
void dijkstra()
{
for(int i=1;i<=n;i++)
dist[i]=(i==1)?0:INF;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++)
{
int mark=-1,mindis=INF;
for(int j=1;j<=n;j++)
{
if(!vis[j]&&dist[j]<mindis)
{
mindis=dist[j];
mark=j;
}
}
vis[mark]=1;
for(int j=0;j<g[mark].size();j++)
{
if(!vis[g[mark][j].fi])
{
dist[g[mark][j].fi]=min(dist[g[mark][j].fi],dist[mark]+g[mark][j].se);
}
}
}
}
堆优化后的Dijkstra:
// 堆优化dijkstra
void dijkstra()
{
memset(dist,63,sizeof(dist));
dist[S]=0;
priority_queue<pII> q; /// -距离,点
q.push(make_pair(0,S));
while(!q.empty())
{
pII tp=q.top(); q.pop();
LL u=tp.second;
if(vis[u]==true) continue;
vis[u]=true;
for(LL i=Adj[u];~i;i=edge[i].next)
{
LL v=edge[i].to;
LL len=edge[i].len;
if(vis[v]) continue;
if(dist[v]>dist[u]+len)
{
dist[v]=dist[u]+len;
q.push(make_pair(-dist[v],v));
}
}
}
}