C++常用模板
Dev C++ 5.11版本下载链接
头文件框架
#include<iostream> //导入C++头文件 #include<stdio.h> //导入C语言头文件 #include<bits/stdc++.h> //导入万能头文件 using namespace std; //使用命名空间,如果不写那么C++里面的函数就不能使用 int main() //主函数 { //程序在这里开始 return 0; //主函数在这里结束 }//大括号表示主函数包含的代码
当然了,你或许还需要以下头文件
#include<bits/stdc++.h> //万能头 #include<math.h> //数学库 #include<string.h> //字符串库
freopen模板
freopen(文件名,操作符,标准输入/输出):文件输入输出函数
文件名和操作符均为双引号字符串,标准输入stdin,标准输出stdout
freopen可以改写标准输入stdin,改成读取文件内容来输入
freopen可以改写标准输出stdout,改成写入内容到文件中输出
freopen("title.in","r",stdin); //改写标准输入stdin,改成"r"读取title.in文件里的内容作为输入 freopen("title.out","w",stdout);//改写标准输出stdout,改成将输出的内容通过写入"w",来写入到title.out文件中
C++常见的数据类型
常见的格式化符号
1 %c 一个字符(char) 2 %d 有符号十进制整数(int)(%ld、%Ld:长整型数据(long),%hd:输出短整形。) 3 %f 单精度浮点数(默认float)、%.nf 这里n表示精确到小数位后n位.十进制计数 4 %p 指针 5 %s 对应字符串char*(%s = %hs = %hS 输出 窄字符) 6 %% 打印一个百分号 7 %I64d 用于INT64 或者 long long 8 %I64u 用于UINT64 或者 unsigned long long
C++的输入方法:scanf和cin
特别注意:scanf仅忽略空格,cin忽略空格和回车
// 使用scanf进行输入 int n; scanf("%d", &n); // 使用cin进行输入 int n; cin >> n;
需要注意的是,scanf和cin的输入格式不同。scanf使用格式化字符串进行输入,而cin使用运算符>>进行输入。对于不同类型的变量,它们的输入格式也不同。下面是一些常见的输入格式:
// 输入一个整数 int n; scanf("%d", &n); cin >> n; // 输入一个浮点数 double x; scanf("%lf", &x); cin >> x; // 输入一个字符串 char s[100]; scanf("%s", s); cin >> s;
分别输入一个整数int、小数double、字符char
int a; double b; char c; cin>>a>>b>>c;
int a; double b; char c; scanf("%d %f %c",&a,&b,&c);
输入字符型char字符串
1 char a[1001]; 2 cin>>a; //遇到空格结束输入 3 scanf("%s\n",&a); //遇到空格结束输入 4 gets(a); //整行输入 5 cin.getline(a,100); //整行输入,最大输入100个字符
输入字符串型string字符串
1 string s; 2 cin>>s; //不能输入空格 3 getline(cin,s); //整行输入
C++输出模板cout和printf:
C++的输出模板通常使用printf和cout两种方式。其中,printf是C语言中的输出函数,而cout是C++中的输出流。下面是两种方式的示例代码:
// 使用printf进行输出 int n = 123; printf("%d\n", n); // 使用cout进行输出 int n = 123; cout << n << endl;
需要注意的是,printf和cout的输出格式也不同。printf使用格式化字符串进行输出,而cout使用运算符<<进行输出。对于不同类型的变量,它们的输出格式也不同。下面是一些常见的输出格式:
// 输出一个整数 int n = 123; printf("%d\n", n); cout << n << endl; // 输出一个浮点数,并保留2位小数 double x = 1.23; printf("%.2lf\n", x); cout << fixed << setprecision(2) << x << endl; // 输出一个字符串 char s[] = "hello"; printf("%s\n", s); cout << s << endl;
printf格式化输出保留小数模板
double n = 3.45; printf("%.2f",n); //n保留2位小数
double a = 3.1415926,b = 99; printf("%.3f %.4f",a,b); //a保留3位小数 ,b保留4位小数
cout输出保留小数模板
double n = 3.42; cout<<fixed<<setprecision(1);//设置输出的浮点数保留1位小数 cout<<n; //3.4
循环1到n的模板
1 for(int i=1;i<=n;i++)
1 int i = 1; 2 while(i<=n) 3 { 4 i++; 5 }
输入n个数/t组数据模板
例如,输入一个数n,接下来有n个数要输入
5
3 1 2 5 6
1 int n,x; cin>>n; 2 for(int i=1;i<=n;i++) //循环n遍 3 { 4 cin>>x; 5 }
1 int n,x; cin>>n; 2 while(n--) //相当于n组数据 3 { //注意,使用while循环结束后n会等于0 4 cin>>x; 5 }
输入n个数存储数组模板
1 int a[1001],n; 2 cin>>n; 3 for(int i=1;i<=n;i++)cin>>a[i];
字符数组的函数方法
定义
char a[1001]; //长度为1001的字符数组
输入
cin>>a; //cin遇到空格或者回车换行会结束输入 gets(a); //整行输入,可以输入空格,遇到回车换行时结束
strlen()获取字符串长度
char a[1001]; cin>>a; int len = strlen(a); //获取a字符串的长度并赋值给len
strcat(a,b)将b字符串拼接到a字符串后面
char a[1001],b[1001]; //定义a,b两个字符数组 cin>>a>>b; //输入两个字符串 cout<<strcat(a,b); //输出a和b拼接的结果
strcpy(a,b)将b字符串的内容复制到a中
strcmp(a,b)比较a和b两个字符串的大小,如果a>b则返回1,如果a<b则返回-1,如果a==b则返回0
字符串常用方法
string s; s.size() /s.length() //表示字符串s的长度 s.substr(star,len) //表示从下标star开始,获取一个长度为len的子串 //获取以空格隔开的子串 int id = 0; //子串开始位置从0开始 for(int i=0;i<s.size();i++) { if(s[i]是空格) { string str = s.substr(id,i-id); //获取从下标id开始,长度为i-id的子串str id = i+1; //更新新的子串位置 } }
常见的数学库函数
abs(x) 计算x的绝对值
sqrt(x) 计算x的平方根
pow(a,b) 计算a的b次方
常见的时间复杂度
https://www.cnblogs.com/jyssh/p/17688352.html
前缀和与差分
设原数组a[],前缀和数组s[],差分数组d[] 前缀和数组s[i] = s[i-1]+a[i] 区间前缀和:求l到r的区间和 s[r]-s[l-1] 差分数组d[i] = a[i]-a[i-1] 差分区间l到r增加p d[l]+=p,d[r+1]-=p 差分还原回原数组a:a[i] = d[i]+a[i-1]
分割整数算法
int sum(int x) //求x的各个位数和 { int res = 0; while(x>0) { res += x%10; //加上个位数 x/=10; //舍弃个位数 } return res; }
进制转化代码
1. 任何一个进制转化成另一个进制都要通过10进制作为中介,除了10进制本身
2.如2转8,需要2->10->8; 反之8转2也要经过8->10->2
3.因此,在这里dc函数是将任何进制转换成10进制,rdc函数是将10进制转换成任何进制
#include<bits/stdc++.h> using namespace std; typedef long long ll; char g[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; char a[1001],s[1001]; //10进制转成x进制后会存储到a中 //输入的非10进制字符串s ll dc(int x) //x进制转成10进制 { ll len = strlen(s),k = 0,n = 0; for(int i=len-1;i>=0;i--) { int o; if(s[i]>'9')o = s[i]-'A'+10; else o = s[i]-'0'; n = n+o*pow(x,k); k++; } return n; //返回十进制数n } int rdc(ll n,int x) //十进制转成x进制 { if(n==0) { a[1] = '0';return 1; //以防n==0的情况 } int len = 0; while(n>0) { a[++len] = g[n%x]; //将n%x的结果存储 n/=x; } return len; } int main() { cin>>s; ll n = dc(16); //获取16进制转成十进制n //cout<<n<<endl; int len = rdc(n,2); //获取n转成2进制后的长度len,数据存储在a数组中 for(int i=len;i>=1;i--)cout<<a[i]; //从len到1就是n转成16进制的结果 return 0; }
一、 2-36进制转十进制方法:strtol()函数
函数原型:long int strtol(const char *nptr, char **endptr, int base)
base是要转化的数的进制,非法字符会赋值给endptr,nptr是要转化的字符
#include <bitset> #include<iostream> using namespace std; int main() { char buffer[20]="10549stend#12"; char *stop; int ans=strtol(buffer,&stop,8); //将八进制1054转成十进制,后面均为非法字符 cout<<ans<<endl; cout<<stop<<endl; return 0; } /* 556 9stend#12 */
注意:
①如果base为0,且字符串不是以0x(或者0X)开头,则按照十进制进行转化。
②如果base为0或者16,并且字符串以0X(或者0x)开头,则x(或者X)被忽略,字符串按16进制转化。
③如果base不等于0和16,并且字符串以0x(或者0X)开头,则x被视为非法字符。
④对于nptr指向的字符串,其开头和结尾的空格被忽略,字符串中间的空格被视为非法字符。
二、 将10进制数转换为任意的n进制数,结果为字符串类型
itoa()函数(可以将一个10进制数转换为任意的2-36进制字符串)
函数原型:char* itoa(int value, char* string, int radix);
例如:itoa(num,str,2); num是一个int型的,是要转换的10进制数,str是转换结果,后面的值为目标进制。
#include<cstdio> #include<cstdlib> int main() { int num = 10; char str[100]; _itoa(num, str, 2); //c++中一般用_itoa,用itoa也行, printf("%s\n", str); return 0; }
位移符 左移<< 和 右移>>
在C++中,位移符主要有两种:左移符(<<)和右移符(>>)。它们用于将二进制数的位向左或向右移动指定的位数。以下是关于这两种位移符的详细介绍:
1. 左移符(<<):将一个数的二进制表示向左移动指定的位数,右侧用0填充。例如,a << b表示将a的二进制表示向左移动b位。这相当于将a乘以2^b。
int a = 4; // 二进制表示:0100 int b = 2; int result = a << b; // 结果为:10000(十进制表示:16)
2. 右移符(>>):将一个数的二进制表示向右移动指定的位数。对于无符号整数,左侧用0填充;对于有符号整数,左侧用符号位填充(即正数用0填充,负数用1填充)。例如,a >> b表示将a的二进制表示向右移动b位。这相当于将a除以2^b(向下取整)。
int a = 16; // 二进制表示:10000 int b = 2; int result = a >> b; // 结果为:0100(十进制表示:4)
左移符1<<k计算2的k次方
可以使用左移符(<<)将数字1向左移动k位。具体来说,表达式1 << k将计算出2的k次方。
例如,如果k等于3,那么1 << k的计算过程如下:
1. 将数字1转换为二进制:0001
2. 将二进制数向左移动3位:1000
3. 将结果转换回十进制:8
因此,1 << 3的结果是8,即2的3次方。
以下是一个C++示例,展示了如何使用位移符计算2的k次方:
#include <iostream> int main() { int k = 3; int result = 1 << k; std::cout << "2^" << k << " = " << result << std::endl; k = 5; result = 1 << k; std::cout << "2^" << k << " = " << result << std::endl; return 0; }
这段代码将输出:
2^3 = 8 2^5 = 32
深度优先搜索
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e3+10,inf = 0x3f3f3f3f; int mp[N][N]; //地图 int vis[N][N]; //标记数组 int dx[8] = {1,0,0,-1,-1,-1,1,1}; int dy[8] = {0,1,-1,0,-1,1,-1,1}; //方向数组,下右左上 int n,m,f,ans; //n行m列,f终点标记,ans路径数 int sx,sy,ex,ey; //起点坐标、终点坐标 void dfs(int x,int y) //在x和y这个点进行搜索 { if(x==ex && y==ey) //找到宝箱 { ans++; //路径数+1 return ; } for(int i=0;i<4;i++) { int tx = x + dx[i]; //下一个方向行坐标 = 当前坐标x + 第i个方向的行增量 int ty = y + dy[i]; //越界判断 if(tx<1 || tx>n || ty<1 || ty>m)continue; //判断tx、ty是否可行 if(mp[tx][ty]!=1 && vis[tx][ty]==0) //tx、ty没有蝙蝠且没有走过 { vis[tx][ty] = 1; //标记tx、ty,证明走过 dfs(tx,ty); //以tx、ty作为起点继续搜索 } } } int main() { //1.输入地图 cin>>n>>m; //n*n地图 for(int i=1;i<=n;i++) //i循环行数 for(int j=1;j<=m;j++) //j循环列数 cin>>mp[i][j]; cin>>ex>>ey; //从起点开始搜索 if(mp[1][1]==1 || mp[ex][ey]==1)//门口有蝙蝠的情况 { cout<<"NO";return 0; } vis[1][1] = 1; //标记起点 dfs(1,1); //输出答案 if(ans)cout<<"YES"; else cout<<"NO"; return 0; }
广度优先搜索
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e3+10,inf = 0x3f3f3f3f; int mp[N][N]; int vis[N][N]; int nex[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; //右下左上方向 int n,m,ans = -1,sum,f; //n行m列,f终点标记,ans答案 struct node{ int x,y,step; //坐标(x,y),当前走了step步 }; void bfs(int sx,int sy,int ex,int ey) //能得到从(sx,sy)到(ex,ey)的最短路ans { queue<node> q; //创建空队列 node t; //初始化起点入队 t.x = sx; t.y = sy; t.step = 0; q.push(t); //起点入队 vis[sx][sy] = 1; //标记起点已走过 while(!q.empty()) //队列非空时执行循环 { //获取队首并让队首出队 node head = q.front(),tail; q.pop(); for(int i=0;i<4;i++) //选择方向 { //获取下一步坐标 = 队首head的坐标+nex第i个方向的增量 int tx = head.x + nex[i][0]; int ty = head.y + nex[i][1]; //越界判断注意边界从0还是1开始,默认从1开始 if(tx<1 || tx>n || ty<1 || ty>m)continue; //判断下一步能不能走,有没有走过 if(mp[tx][ty]!=1 && vis[tx][ty] == 0) { //结点(tx,ty)入队并标记 队尾的步长 = 队首的步长+1 tail = {tx,ty,head.step+1}; vis[tx][ty] = 1; //标记结点(tx,ty) q.push(tail); //结点tail入队 } if(tx==ex && ty==ey) //判断当前结点是否为终点 { f = 1; ans = tail.step; //队尾的步长是起点到终点的最短路 break; } } if(f)break; //结束搜索 } } int main() { //输入n行m列地图,捏造起点终点 cin>>n>>m; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>mp[i][j]; int sx,sy,ex,ey; cin>>sx>>sy>>ex>>ey; //传入起点终点,进行搜索 bfs(sx,sy,ex,ey); //输出答案 cout<<ans; return 0; }
埃氏筛
#include<bits/stdc++.h> #define f(i,s,e) for(int i = s; i <= e; i++) #define ll long long using namespace std; const int N = 1e7+10,inf = 0x3f3f3f3f; int vis[N]; int p[N],k; void alsh(int n) { for(int i = 2; i <= n; i++) { if(vis[i] == 0) { p[++k] = i; for(int j = i * 2; j <= n; j+=i) vis[j] = 1; } } } int main() { alsh(1e6); for(int i = 1; i <= k; i++) cout << p[i] << endl; return 0; }
欧拉线性筛素数
#include<bits/stdc++.h> using namespace std; const int N = 1e7; bool vis[N] = {1,1}; int p[500001],k; //可以获得总共k个素数存在p数组里 void ola() { for(int i=2;i<=N;i++) //找出2到N中所有的素数 { if(vis[i]==0)p[++k] = i; for(int j=1;j<=k&&p[j]*i<=N;j++) { vis[p[j]*i] = 1; if(i%p[j]==0)break; } } } int main() { ola(); return 0; }
dijkstra算法模板
特别注意minn==inf的判断要写,不然很容易RE
#include<bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; int g[2005][2005],vis[2005],dis[2005]; //g[i][j]表示从i到j的距离 //vis[i]表示标记第i点作为中间点 //dis[i]表示起点到i的最短距离 int n,m,t; void dij(int s) //s起点 { for(int i=1;i<=n;i++)dis[i] = g[s][i]; //初始化起点到i的距离为g[s][i] dis[s] = 0;vis[s] = 1; //标记起点,初始化到地点的距离为0 int minn,pos; //pos是没有标记过的从起点出发可到达的最短距离的点 for(int i=1;i<=n;i++) { minn = inf; for(int j=1;j<=n;j++) { if(vis[j]==0&&dis[j]<minn) { minn = dis[j];pos = j; //寻找可以更新最短路的点 } } if(minn==inf)break; //如果没有更新成功,那么接下来也不会更新了 vis[pos] = 1; for(int j=1;j<=n;j++) { if(dis[j]>dis[pos]+g[pos][j]) dis[j] = dis[pos]+g[pos][j]; //以pos为中间点,更新最短路的所有点 } } }
floyd算法模板
#include<bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; int g[2005][2005]; //g[i][j]表示从i到j的最短距离 int n,m,t; void floyd() { for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(g[i][j]>g[i][k]+g[k][j]) //如果地图g上从i到j的距离大于从i到k+k到j的距离 g[i][j] = g[i][k]+g[k][j]; //更新最短距离 }
spfa算法模板
5778: 城市路 dijkstra/spfa(邻接矩阵/邻接表)
并查集模板
int f[N]; //f[x] = y 表示x的父结点是y for(int i=1;i<=n;i++)f[i] = i;//初始化每个人的祖先是自己 int find(int x) //查找,寻找x的祖先节点,并将f[x]更改为祖先 { if(f[x]!=x)f[x] = find(f[x]); return f[x]; } void merger(int x,int y) //合并,将x合并到y { int fx = find(x); int fy = find(y); f[fx] = fy; //合并的是x和y的祖先 }
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e3+10,inf = 0x3f3f3f3f; int n,m;//n个点,m个关系 int f[N],a[N];//f[i] = x表示i的祖先是x ,a[i] = x i所在的关系网有x人 int find(int x) //找到x的祖先 x=2 f[x] = 3 { if(f[x]!=x)f[x] = find(f[x]); //如果x的爹不是自己,那么去寻找爹中爹 return f[x]; } void merger(int x,int y) //合并x和y两个集合 { int fx = find(x); int fy = find(y); if(a[fx]<a[fy])swap(fx,fy); //交换位置,保证fx所在的关系网人数是比较多的 a[fx] = a[fx] + a[fy]; f[fy] = fx; //fy的祖先是fx } int main() { cin>>n>>m; for(int i=1;i<=n;i++)f[i] = i,a[i] = 1; //最开始的时候,每个人的祖先是自己 for(int i=1;i<=m;i++) { int x,y; cin>>x>>y; //x和y是认识的 if(find(x)!=find(y)) //如果x的祖先和y的祖先是不认识的 merger(x,y); //那么合并x和y的关系 } cout<<"并查集结束后的关系网:"<<endl; for(int i=1;i<=n;i++) { printf("%d的祖先是%d,%d所在的家族人数为%d\n",i,f[i],i,a[find(i)]); } int sum = 0,maxx = 0; //总共有sum个家族,家族最大人数为maxx for(int i=1;i<=n;i++) { if(f[i]==i)sum++; maxx = max(maxx,a[find(i)]); //比较maxx和第i个家族人数的大小 } printf("共有%d个家族,家族最大人数为%d\n",sum,maxx); return 0; }
最小生成树prim&&kruskal
prim:Prim算法是一种贪心算法,它从一个顶点开始,逐步扩展生成树,直到生成整个图的最小生成树。具体步骤如下:
1. 选取一个起始顶点,将其加入生成树中。
2. 找到与生成树相邻的所有顶点中,权值最小的边所连接的顶点,将其加入生成树中。
3. 重复步骤2,直到生成整个图的最小生成树。
#include<bits/stdc++.h> using namespace std; const int N=1e3+10,inf=0x3f3f3f3f; int dis[N],vis[N],g[N][N],f[N]; //dis[i]表示第i个点在生成树中的最小权值 //vis[i]表示第i个点是否加入了最小生成树 //f[i]表示和第i个点相连的点 //g[i][j]表示从i到j的距离 int n,m,ans; //n个点,m条边 void Prim() { memset(vis,0,sizeof(vis)); ans = 0; //最小生成树的代价 vis[1]=1; for(int i=2;i<=n;i++)dis[i]=g[1][i],f[i]=1; for(int i=2;i<=n;i++) { int minc=inf; int p=-1; for(int j=1;j<=n;j++) if(!vis[j]&&minc>dis[j]) minc=dis[j],p=j; if(minc==inf)break; vis[p]=1; //printf("%d %d %d\n",f[p],p,dis[p]);//输出p点在生成树的最小边和权值 ans+=dis[p]; //加上权值 for(int j=1;j<=n;j++) if(!vis[j]&&dis[j]>g[p][j]) { dis[j]=g[p][j],f[j]=p; } } } int main() { cin>>n>>m; memset(g,inf,sizeof g); //初始化地图的为极大值 for(int i=1;i<=m;i++) //输入m条边构图 { int x,y,z;cin>>x>>y>>z; g[x][y] = g[y][x] = z; } Prim(); //调用普利姆算法 cout<<ans; //输出权值 return 0; }
kruskal:Kruskal算法也是一种贪心算法,它将所有边按照权值从小到大排序,然后依次加入生成树中,直到生成整个图的最小生成树。具体步骤如下:
1. 将所有边按照权值从小到大排序。
2. 依次加入每条边,如果加入后形成环,则不加入。
3. 重复步骤2,直到生成整个图的最小生成树。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e3+10,inf = 0x3f3f3f3f; struct node{ int x,y,z; }a[N]; int f[N]; int n,m; bool cmp(node a,node b) { return a.z<b.z; //按权值z从小到大 } int find(int x) { if(f[x]!=x)f[x] = find(f[x]); return f[x]; } void merger(int x,int y) { int fx = find(x); int fy = find(y); f[fy] = fx; } void kruaskal() { for(int i=1;i<=n;i++)f[i] = i; //并查集初始化 sort(a+1,a+1+m,cmp); //对m条边进行按权值从小到大排序 int sum = 0,ans = 0; //sum是当前生成树中边的数量,ans是当前生成树的最小权值 for(int i=1;i<=m;i++) { if(find(a[i].x)!=find(a[i].y)) //如果x,y两个点未连接 { sum++; ans+=a[i].z; merger(a[i].x,a[i].y); //合并 printf("%d %d %d\n",a[i].x,a[i].y,a[i].z); if(sum==n-1)break;//只需要n-1条边即可生成树 } } cout<<ans<<endl; } int main() { cin>>n>>m; for(int i=1;i<=m;i++)cin>>a[i].x>>a[i].y>>a[i].z; kruaskal(); return 0; }
拓扑排序
拓扑排序是一种对有向无环图进行排序的算法。它的基本思想是将图中的节点按照它们之间的依赖关系进行排序,使得每个节点都排在它的后继节点之前。这种排序可以用来解决很多实际问题,比如任务调度、编译顺序等。
在实现拓扑排序时,可以使用一个队列来存储入度为0的节点。每次从队列中取出一个节点,并将它的后继节点的入度减1。如果某个节点的入度变为0,就将它加入队列中。重复这个过程,直到队列为空为止。最终得到的序列就是拓扑排序的结果。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e4+10,inf = 0x3f3f3f3f; vector<int>g[N]; int n,m,x,y,rd[N]; queue<int>q; int topsort() { int num = 0; for(int i=0;i<n;i++)if(rd[i]==0)q.push(i);//先将入度为0的点加入队列 while(!q.empty()) { int x = q.front();q.pop();num++; //队首出队,数量+1 for(int i=0;i<g[x].size();i++) //遍历和队首x相邻的点g[x] { rd[g[x][i]]--; //将和队首x相邻的点的入度-1 if(rd[g[x][i]]==0)q.push(g[x][i]); //如果出现入度为0的点,就入队 } } if(num==n)return 1; //如果在队列中刚好所有点都出现了,那么就证明无环 else return 0; } int main() { while(cin>>n>>m) { if(!n && !m)break; for(int i=0;i<n;i++)g[i].clear(); memset(rd,0,sizeof(rd)); memset(cnt,0,sizeof(cnt)); for(int i=1;i<=m;i++) { cin>>x>>y; g[x].push_back(y);rd[y]++; } if(topsort()) //返回0证明存在环 { cout<<"YES"<<endl; } else cout<<"NO"<<endl; } return 0; }
单调队列dp
求n个数中长度为k的滑动窗口中的最小值、最大值
#include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #define maxn 1000100 using namespace std; int q[maxn], a[maxn]; int n, k; void getmin() { // 得到这个队列里的最小值,直接找到最后的就行了 int head = 0, tail = 0; for (int i = 1; i < k; i++) { while (head <= tail && a[q[tail]] >= a[i]) tail--; q[++tail] = i; } for (int i = k; i <= n; i++) { while (head <= tail && a[q[tail]] >= a[i]) tail--; q[++tail] = i; while (q[head] <= i - k) head++; printf("%d ", a[q[head]]); } } void getmax() { // 和上面同理 int head = 0, tail = 0; for (int i = 1; i < k; i++) { while (head <= tail && a[q[tail]] <= a[i]) tail--; q[++tail] = i; } for (int i = k; i <= n; i++) { while (head <= tail && a[q[tail]] <= a[i]) tail--; q[++tail] = i; while (q[head] <= i - k) head++; printf("%d ", a[q[head]]); } } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); getmin(); printf("\n"); getmax(); printf("\n"); return 0; }
树状数组
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6+10,inf = 0x3f3f3f3f; ll c[N]; //c是树状数组,其中c[i] = a[i-2^k+1]+...+a[i](k为在i的二进制形式下末尾0的个数) int n,m,k,a,b; int lowbit(int x) //用lowbit(x)表示2^k k为x在二进制形式下末尾0的个数 { return x&(-x); } void updata(int x,int v) //在序列第x个位置上加上v,并在树状数组中修改元素 { while(x<=n) { c[x]+=v; x+=lowbit(x); } } ll sum(int x) //计算第1个数到第x个数的和 { ll res = 0; while(x>0) { res+=c[x]; x-=lowbit(x); } return res; } int main() { int v; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&v); updata(i,v); //开始每个元素初始值为0,我们读入数列时直接在对应的位置增加v即可 } for(int i=1;i<=m;i++) { scanf("%d%d%d",&k,&a,&b); if(k==2) printf("%lld\n",sum(b)-sum(a-1)); //计算区间[a,b]的和时,可以分别算出[1,b]和[1,a-1]的和,相减即为[a,b]的和 else updata(a,b); //在第a个位置上加上b } return 0; }
RMQ求区间最大最小值
输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y,要求你说出 X 到 Y 这段区间内的最大数
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5+10,inf = 0x3f3f3f3f,lgn = 20; int lg[N],f[N][lgn+5],a[N]; int n,m,x,y; void rmq() { lg[0] = -1; for(int i=1;i<=n;i++) f[i][0] = a[i],lg[i] = lg[i>>1]+1; //预处理出长度为1-n的lg值 for(int j=1;j<=lgn;j++) //计算f[i][j] for(int i=1;i+(1<<j)-1<=n;i++) //区间边界不超过n f[i][j] = max(f[i][j-1],f[i+(1<<j-1)][j-1]); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]); rmq(); //预处理rmq while(m--) { scanf("%d%d",&x,&y); int s = lg[y-x+1]; //求lg2(y-x+1)下取整的值 printf("%d\n",max(f[x][s],f[y-(1<<s)+1][s])); } return 0; }
线段树模板链接
https://www.cnblogs.com/jyssh/p/17685734.html
KMP模板
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e3+10,inf = 0x3f3f3f3f; int nex[N]; //nex[j]的意思是当子串的第j个字符和主串的第i个字符不匹配时,我们应该从子串的nex[j]字符开始重新匹配 string a,b; /* kmp指针回退 j = nex[j - 1] 根本原因:子串的指针j总是表示我们即将要匹配子串的第j个字符,所以如果子串[j]和主串[i]不匹配 那么我们自然是要从子串的nex[j - 1]字符开始重新匹配 */ void get_next(string b) { int j = 0; //前缀末尾 for(int i = 1; i < b.size(); i++) { //指针回退 while(j > 0 && b[i] != b[j]) j = nex[j - 1]; //前缀和后缀的末尾相同则计数 if(b[j] == b[i]) j++; nex[i] = j; } } int kmp(string a,string b) //获取子串在主串中第一次出现的位置 { int j = 0; //子串指针j,主串指针i for(int i = 0; i < a.size(); i++) { while(j > 0 && a[i] != b[j]) j = nex[j - 1]; //3.指针回退 if(a[i] == b[j]) j++; //1.当前子串和主串匹配,那么继续匹配子串的下一个 if(j == b.size()) //2.如果匹配完了,那么成功匹配的位置是:主串当前长度i - 子串长度b.size() + 1 { //这里是返回子串在主串中第一次出现的位置,如果要解决其他问题,可以在这里进行修改 return i - b.size() + 1; } } return -1; //没有匹配成功返回-1 } int main() { getline(cin,a); getline(cin,b); get_next(b); cout << kmp(a,b); return 0; }