本篇文章主要是Mangata平时写代码所用到的代码模板库,如有不对请在评论区指出
先放一个我的 常数优化的博客: 传送门
再放一个我的 代码格式博客: 传送门
快读快输模板
有些题目可能会丧心病狂的卡常,这个时候我们可以使用快读快输这种方法优化我们程序的常数。
/*
作者:Mangata
int的快读快输
*/
#include<cstdio>
inline int read()//快读
{
int x = 0;
bool fg = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') fg = 0;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}
if(fg)
return x;
return ~(x - 1);
}
inline int write(int x)//快输
{
if(x < 0)
{
x = ~(x - 1);
putchar('-');
}
int s[20];
int top = 0;
while(x)
{
s[++top] = x%10;
x /= 10;
}
if(!top) s[++top] = 0;
while(top)
putchar(s[top--] + '0');
}
int main()
{
while(1){
int n = read();
write(n);
putchar('
');
}
return 0;
}
_int128模板
能够存储128位2进制的数(算是微大数)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline void scan(__int128 &x)//输入
{
x = 0;
int f = 1;
char ch;
if((ch = getchar()) == '-') f = -f;
else x = x*10 + ch-'0';
while((ch = getchar()) >= '0' && ch <= '9')
x = x*10 + ch-'0';
x *= f;
}
inline void print(__int128 x) {
if(x < 0) {
x = -x;
putchar('-');
}
if(x > 9)
print(x/10);
putchar(x % 10 + '0');
}
int main()
{
__int128 a,b,c;
scan(a);
scan(b);
c = a+b;
print(c);
return 0;
}
二分模板
二分的模板有很多,最好记住并使用一种二分模板就行,这是Mangata最爱的二分模板
/*
作者:Mangata
二分模板返回大于x的第一个位置
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 1005
using namespace std;
int a[N],n,q;
int find(int l,int r,int key)//l为-1,r为数组长度
{
while(l + 1 < r)
{
int mid = l + r>>1;
if(a[mid] <= key)
l = mid;
else
r = mid;
}
return r;//返回大于Key的第一个位置
}
int main()
{
int k;
scanf("%d%d",&n,&q);
for(int i = 0; i < n; ++i)
scanf("%d",&a[i]);
for(int i = 0; i < q; ++i)
{
scanf("%d",&k);
printf("%d
",find(-1,n,k));
}
}
背包模板
目前只写三种常用的背包模板(01、完全、多重),主要也是其他的背包都比较抽象,不好直接写代码。
01背包
/*
作者:Mangata
01背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d",&w[i],&v[i]);
for(int i = 1; i <= n; ++i)
{
for(int j = V; j >= w[i]; --j)
{
f[j] = max(f[j],f[j-w[i]]+v[i]);
}
}
printf("%d
",f[V]);
return 0;
}
完全背包
/*
作者:Mangata
完全背包问题模板 滚动数组优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d",&w[i], &v[i]);
for(int i = 1; i <= n; ++i)
{
for(int j = w[i]; j <= V; ++j)
{
f[j] = max(f[j], f[j-w[i]]+v[i]);
}
}
printf("%d
",f[V]);
return 0;
}
多重背包
/*
作者:Mangata
多重背包问题模板 二进制枚举优化
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int f[N];
int v[N],w[N],p[N];
int n,V;
int main()
{
scanf("%d%d",&n,&V);
for(int i = 1; i <= n; ++i) scanf("%d%d%d",&w[i],&v[i],&p[i]);
for(int i = 1; i <= n; ++i)
{
int num = min(p[i],V/w[i]);
for(int k = 1; num > 0; k <<= 1)
{
if(k > num)
k = num;
num -= k;
for(int j = V; j >= w[i]*k; --j)
f[j] = max(f[j], f[j-w[i]*k]+v[i]*k);
}
}
printf("%d
", f[V]);
return 0;
}
Manacher模板
Manacher算法主要应用于回文字符串上面,通过字符串翻倍我们能很轻松的通过对称的关系找到子字符串的回文长度。
/*
作者:Mangata
manacger模板
*/
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define N 210005
char S[N],str[N*2+5];
int len1,len2,ans,p[N*2+5];
void init() {//数组初始化,即数组长度翻倍
str[0] = '$';//为了防止数组越界
str[1] = '#';
for(int i = 0; i < len1; ++i){
str[i * 2 + 2] = S[i];
str[i * 2 + 3] = '#';
}
len2 = len1 * 2 + 2;
str[len2] = '*';
}
void manacher() {//manacher
init();
int id = 0,mx = 0;//mx表示的是当前计算回文串最右边字符的最大值
for(int i=0; i < len2; ++i){
p[i]=mx > i?min(p[id*2-i],mx-i) : 1;//p[i]=mx>i?min(p[id*2-i],m-i):1;
for(; str[i+p[i]] == str[i-p[i]]; p[i]++);//如果字符串对称则对称长度增加
if(p[i]+i > mx)//如果大于当前的最大长度则更新
mx = p[i]+i, id = i;
}
}
int main() {
while(~scanf("%s",S)){
len1 = strlen(S);
manacher();
int maxlen = 0;
for(int i = 0; i < len2; ++i)
maxlen = max(p[i], maxlen);
printf("%d
",maxlen-1);
}
return 0;
}
KMP模板
KMP是一个字符串匹配算法,通过此算法能达到线性的匹配时间复杂度,通过对字串求next数组,让我们字符串不回溯(或者说是减少步骤)
/*
作者:Mangata
manacger模板
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10005
int nextt[N];
char S[N],T[N];
int len1,len2;
void get_next() {//获取next数组
int i = 0,j = -1;
nextt[0] = -1;//很重要
while(i < len1){
if(j == -1 || S[i] == S[j])
nextt[++i] = ++j;
else
j = nextt[j];
}
}
int kmp(){//返回的是子串在主串中存在的次数
int cnt = 0;//表示的是子串在主串中存在的次数
int i = 0,j = 0;
get_next();
while(i < len2)
{
if(j == -1 || T[i] == S[j])
i++,j++;
else
j = nextt[j];
if(j == len1)//表示的是子串在主串中存在的次数
j=0,cnt++;//子串指针归零
}
return cnt;
}
int main(){
while(~scanf("%s",T))
{
if(T[0]=='#')
break;
scanf("%s",S);
len1 = strlen(S);
len2 = strlen(T);
printf("%d
",kmp());
}
return 0;
}
并查集
并查集是一种集合数据结构,通过并查集我们可以快速查询两个元素是否是一个集合,下面是Mangata常用的板子
/*
作者:Mangata
路径压缩并查集
*/
#include<cstdio>
const int N = 10005;//节点数
int fa[N],n;
void init(int len) {//初始化,先让每个位置的父节点等于自身
for(int i=0; i<=n; ++i)
fa[i] = i;
}
int find(int x) {//查找x的祖先节点
int t = x;//路径压缩
while(t != fa[t])
t = fa[t];
while(x != fa[x]) {
int temp = fa[x];
fa[x] = t;
x = temp;
}
return x;
}
void merge(int a,int b) {//将x和y合并
a=find(a),b=find(b);
if(a != b) {
fa[b] = a;
n--;
}
}
int main()
{
int t,m,a,b;
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
init(n);
for(int i = 1; i <= m; ++i) {
scanf("%d%d",&a,&b);
merge(a,b);
}
printf("%d
",n);//输出不同类别的总数目
}
return 0;
}
GCD
GCD的四种方式,辗转相除法和更相减损法的循环写法和递归写法,但是一般来说要用gcd,直接调用__gcd函数就可
/*
作者:Mangata
GCD的四种写法
*/
#include<cstdio>
int gcd_1(int a,int b){//辗转相除法 循环写法
while(b > 0) {
int t = a%b;
a = b;
b = t;
}
return a;
}
int gcd_2(int a,int b) {//辗转相除法 递归写法
return b?gcd_2(b,a%b) : a;
}
int gcd_3(int a,int b) {//更相减损法 递归写法
if(a == 0)
return b;
if(b == 0)
return a;
if(a == b)
return a;
if(a > b)
return gcd_3(a-b,b);
if(a < b)
return gcd_3(b-a,a);
}
int gcd_4(int a,int b) {//更相减损法 循环写法
if(a == 0)
return b;
if(b == 0)
return a;
while(a != b) {
if(a > b)
a -= b;
else
{
int t = a;
a = b - a;
b = t;
}
}
return a;
}
int main()
{
int a,b;
while(~scanf("%d%d",&a,&b))
printf("gcd_1=%d
gcd_2=%d
gcd_3=%d
gcd_4=%d
",gcd_1(a,b),gcd_2(a,b),gcd_3(a,b),gcd_4(a,b));
}
拓展GCD
关于拓展GCD,我觉得比较核心的是贝祖定理,ax+by=gcd(a,b),方便我们求解不定方程,板子选取的题目是青蛙的约会
青蛙的约会
#include<cstdio>
#include<algorithm>
#define ll long long
//返回的是GCD(a,b)
ll extgcd(ll a, ll b, ll &x0, ll &y0) {//注意这里是引用
if (!b) {
x0 = 1, y0 = 0;
return a;
}
ll d = extgcd(b, a%b, x0, y0);
ll t = x0;
x0 = y0;
y0 = t - a / b * y0;
return d;
}
int main()
{
ll x,y,m,l,x0,y0,n;
while(~scanf("%lld%lld%lld%lld%lld",&x, &y, &m, &n, &l)) {
x0 = 0, y0 = 0;
ll c = x - y;
ll a = n - m;
if(a < 0) {
a = -a;
c = -c;
}
ll d = extgcd(a, l, x0, y0);
if (c % d)
puts("Impossible");
else
printf("%lld
",(c/d*x0 % (l/d) + l/d) % (l/d));
}
return 0;
}
素数判断
朴素素数判断
/*
作者:Mangata
朴素素数判断模板
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
bool is_prime(int x) {
if(x == 0 || x == 1)
return false;
for(int i = 2; i*i <= x; ++i) {
if(x % i == 0)
return false;
}
return true;
}
int main()
{
while(~scanf("%d",&x)) {
if (is_prime(x))
puts("Yes");
else
puts("No");
}
return 0;
}
埃式筛
/*
作者:Mangata
埃式筛模板
*/
//时间复杂度 :O(n*log(log(n)))
#include<cstdio>
#include<algorithm>
#include<cstring>
const int N = 10000005;
bool vis[N] = {true,true};//初始化
int main()
{
int n,x;//离线处理
for(int i = 2; i*i <= N; ++i) {
if (!vis[i]) {
for(int j = i*2; j <= N; j += i) //把素数的倍数筛掉
vis[j] = true;
}
}
while(~scanf("%d",&x)) {
if (vis[x])
puts("No");
else
puts("Yes");
}
return 0;
}
欧拉筛
/*
作者:Mangata
欧拉筛模板
*/
//时间复杂度为 O(n)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
const int N = 10000005;
int prime[N];
bool vis[N];
void get_prime() {
memset(vis,true,sizeof vis);
memset(prime,0,sizeof prime);
vis[0] = vis[1] = false;
for(int i = 2; i <= N; ++i) {
if (vis[i]) {
prime[++prime[0]] = i;
}
for(int j = 1;j <= prime[0] && i*prime[j] <= N; ++j) {
vis[i * prime[j]] = false;
if (i % prime[j] == 0) //避免重复筛选
break;
}
}
}
int main()
{
int n;
get_prime();
while(~scanf("%d",&n)) {
if (vis[n])
puts("Yes");
else
puts("No");
}
return 0;
}
最小生成树
kruskal
最小生成数基于并查集的写法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 205
struct node{
int from,to;
long long cost;
}E[N*N];
int fa[N],n,m;
bool cmp(node a,node b){
return a.cost<b.cost;
}
int find(int x){
int k = x;
while(k != fa[k])
k = fa[k];
while(x != fa[x]) {
int t =fa[x];
fa[x] = k;
x = t;
}
return x;
}
void init(){
for(int i = 1; i <= n; ++i)
fa[i] = i;
}
int same(int x,int y){
return find(x) == find(y);
}
void merge(int a,int b){
a = find(a);
b = find(b);
if(a != b)
fa[b] = a;
}
long long kruskal(){
long long ans = 0;
sort(E+1,E+m+1,cmp);
for(int i = 1; i <= m; ++i){
if(same(E[i].from,E[i].to))
continue;
merge(E[i].from,E[i].to);
ans += E[i].cost;
}
return ans;//返回的是最小生成树的代价
}
int main(){
int u,v,w;
while(~scanf("%d%d",&m,&n)&&m)
{
init();
for(int i = 1; i <= m; ++i){
scanf("%d%d%d",&u,&v,&w);
E[i].from = u,E[i].to = v,E[i].cost = w;
}
long long k = kruskal();
int loc = find(1);
for(int i = 2; i <= n; ++i)
if(find(i) != loc){
k = -1;
break;
}
if(k == -1)
puts("?");
else
printf("%lld
",k);
}
return 0;
}
prim
最小生成树基于prim的写法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
#define P pair<int, int>
#define INF 0x3f3f3f3f
const int N = 1005;
int mp[N][N];
bool vis[N];
int dis[N];
int n,m;
//返回的是一个生成树每条边的和
int prim(int s) {
for(int i = 1; i <= n ; ++i) {
dis[i] = INF;
vis[i] = false;
}
dis[s] = 0;
int ans = 0;
while(true) {
int v = -1;
for(int u = 1; u <= n; ++u) {
if(!vis[u] && (v == -1 || dis[u] < dis[v])) {
v = u;
}
}
if(v == -1)
break;
vis[v] = true;
ans += dis[v];
//松弛操作 更新最短路径
for(int u = 1; u <= n; ++u) {
dis[u] = min(dis[u], mp[u][v]);
}
}
return ans;
}
int main()
{
int u,v,cost;
while(~scanf("%d%d",&m, &n) && m) {
memset(mp,INF,sizeof mp);
for(int i = 0; i < m; ++i) {
scanf("%d%d%d",&u, &v, &cost);
if(mp[u][v] > cost)
mp[u][v] = mp[v][u] = cost;
}
int ans = prim(1);
if(ans < INF) {
printf("%d
",ans);
} else {
puts("?");
}
}
return 0;
}
快速幂&快速乘
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
//快速幂
ll quick_pow(ll x, ll n, ll mod) {
ll res = 1;
while(n > 0) {
if(n & 1) res = (res * x)% mod;
x = (x * x) % mod;
n >>= 1;
}
return res;
}
//快速乘
ll quick_pow2(ll x, ll n, ll mod) {
ll res = 0;
x%= mod;
n%= mod;
while(n) {
if(n & 1) {
res = (res + x) % mod;
}
x = (x%mod + x%mod) % mod;
n >>= 1;
}
return res%mod;
}
int main()
{
ll a,b,c,n;
// while(~scanf("%lld%lld%lld",&a,&b,&c)) {
// printf("%lld
",quick_pow(a,b,c));
// }
ll key,temp;
while(~scanf("%lld%lld",&n,&c)) {
scanf("%lld",&key);
key %= c;
for(ll i = 1; i < n; ++i) {
scanf("%lld",&temp);
key = quick_pow2(key,temp,c);
}
printf("%lld
",key);
}
return 0;
}
线段树
单点修改,区间查询
例题:HDU1754
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2000005;
int n,m;
int a[N],tree[N << 2];
void push_up(int k) {
tree[k] = max(tree[k<<1],tree[k<<1|1]);
}
void build(int k, int l,int r) {
if(l == r) {
tree[k] = a[l];
}
else {
int mid = l + ((r-l)>>1);
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
push_up(k);
}
}
void updata(int p,int v,int l,int r,int k) {
if(l == r) {
a[p] += v, tree[k] += v;
}
else {
int mid = l + ((r-l)>>1);
if(p <= mid) {
updata(p,v,l,mid,k<<1);
}
else {
updata(p,v,mid+1,r,k<<1|1);
}
push_up(k);
}
}
int query(int L, int R,int l,int r,int k) {
if(L <= l && R >= r) {
return tree[k];
}
else {
int ans = -INF;
int mid = l+r >>1;
if(L <= mid) {
ans = max(ans,query(L,R,l,mid,k<<1));
}
if(R > mid) {
ans = max(ans,query(L,R,mid+1,r,k<<1|1));
}
return ans;
}
}
int main()
{
while(~scanf("%d%d",&n,&m)) {
for(int i = 1;i <= n; ++i) {
scanf("%d",&a[i]);
}
build(1,1,n);
char op;
int l,r;
while(m--) {
cin>>op;
if(op == 'Q') {
scanf("%d%d",&l,&r);
printf("%d
",query(l,r,1,n,1));
}
else if(op == 'U'){
scanf("%d%d",&l,&r);
updata(l,r-a[l],1,n,1);
}
}
}
return 0;
}
To Be Continue……