联赛难度常用模板
基础技能:
头文件与输入输出:
#include<cstdio> //输入输出头文件
#include<cstring> //字符串头文件,数组整体赋值头文件
#include<cstdlib> //随机数头文件
#include<algorithm>//快排头文件
#include<cmath> //sqrt等数学函数头文件(abs之类的建议手打,因为自带的可能出锅)
#include<ctime> //随机数初始化头文件
using namespace std;//开了algorithm/其它不以c开头的头文件时要打的东西
char str[10];
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d %d %d\n",a,b,a+b);
long long c,d;
scanf("%lld%lld",&c,&d);
printf("%lld %lld %lld\n",c,d,c+d);
scanf("\n%s",str+1); //注意+1代表从第一位开始(若不写+1则默认从第零位开始)
int len=strlen(str+1); //前面有+1,求长度时也要+1
for (int i=1;i<=len;i++) printf("%c",str[i]); //字符串的输出建议逐个字符输出
printf("\n");
double x,y;
scanf("%lf%lf",&x,&y);
printf("%.6lf\n",x+y); //注意怎么保留小数
}
数组整体赋值、清空:
#include<cstdio>
#include<cstring>//记得一定要开这个库(不开本机可能不会报错,但交上去会挂)
int a[10010],b[10010];
int main()
{
memset(a,0,sizeof(a)); //a整体赋值为0
memset(a,100,sizeof(a));//a整体赋值为一个较大值
memcpy(a,b,sizeof(b));//a整体赋值为b数组每一位对应的值
return 0;
}
存边方式:
#include<cstdio>
int tot;
int las[5010],nxt[10010],to[10010]; //注意nxt,to的两倍空间(双向边时),注意变量名称不要写成英文全拼(不同编译环境下容易出锅)。Las[x]表示编号为x的点最后一次出现的位置。Nxt[tot]表示当前位置tot对应的点所跳到的上一个位置,to[tot]表示当前位置tot对应的点所连向的点。找与x相连的所有点,就是从las[x]一直往前跳。
void insert(int x,int y)
{
nxt[++tot]=las[x];
las[x]=tot;
to[tot]=y;
}
int main()
{
int m;
scanf("%d",&m);
int x,y;
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
insert(x,y); insert(y,x); //双向边
}
x=1;
for (int i=las[x];i;i=nxt[i]) //枚举与x相连的所有点并输出
{
printf("%d\n",to[i]);
}
}
快排:
#include<cstdio>
#include<algorithm>//头文件
using namespace std;//记得加上
int a[1010];
bool cmp(int x,int y)
{
return x>y; //符号是小于就是从小到大排序,符号是大于就是从大到小排序。
}
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n,cmp);// 不写cmp就默认从小到大
for (int i=1;i<=n;i++) printf("%d ",a[i]);
return 0;
}
结构体多关键字快排:
#include<cstdio>
#include<algorithm>
using namespace std;
struct gjy{
int x,num;
} a[1010];
bool cmp(gjy a,gjy b)
{
return (a.x<b.x) || (a.x==b.x && a.num<b.num); .x为第一关键字,.num为第二关键字,均从小到大
}
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i].x),a[i].num=i;
sort(a+1,a+1+n,cmp);
for (int i=1;i<=n;i++) printf("%d ",a[i].x);
return 0;
}
EOF:
当题目说输入若干组数据,以文件结束符为结尾时。
#include<cstdio>
int main()
{
int n,x;
while (scanf("%d",&n)!=EOF)
{
scanf("%d",&x);
printf("%d %d\n",n,x);
}
return 0;
}
水分/打表/找规律:
- 伪贪心有时可以水很多分
- 数学题不会就打表找规律
- 数据范围小的可以打表建立数据库
随机数://造数据或者打玄学做法时可使用,不要求掌握
#include<cstdio>
#include<cstdlib>//专属头文件
#include<ctime>//专属头文件
int main()
{
srand(time(0)); //随机种子初始化
int x=rand()%10+1; //rand的范围在本机应该是3万多
printf("%d\n",x);
return 0;
}
图论:
最短路(spfa):
var a:array[1..1000,0..1000]of longint;
f:array[1..1000,1..1000]of longint;
dis:array[1..1000]of longint;
data:array[1..100000]of longint;
bz:array[1..1000]of boolean;
x,y,t,n,m,i,j,l,r:longint;
begin
readln(n,m);
fillchar(f,sizeof(f),10);
for i:=1 to m do
begin
readln(x,y,t);
if f[x,y]<168430090 then
begin
if f[x,y]>t then f[x,y]:=t;
end else
begin
inc(a[x,0]);
a[x,a[x,0]]:=y;
f[x,y]:=t;
end;
end;
fillchar(data,sizeof(data),0);
fillchar(dis,sizeof(dis),10);
fillchar(bz,sizeof(bz),false);
i:=1;
l:=0;
r:=1;
data[1]:=i;
dis[i]:=0;
while l<r do
begin
inc(l);
t:=data[l];
for j:=1 to a[t,0] do
if dis[t]+f[t,a[t,j]]<dis[a[t,j]] then
begin
dis[a[t,j]]:=dis[t]+f[t,a[t,j]];
if bz[a[t,j]]=false then
begin
bz[a[t,j]]:=true;
inc(r);
data[r]:=a[t,j];
end;
end;
bz[t]:=false;
end;
for j:=1 to n do
if dis[j]<f[i,j] then f[i,j]:=dis[j];
for i:=2 to n do writeln(f[1,i]); //这是选择1为起点到其它所有点的最短路
end.
最小生成树:
#include<cstdio>
#include<algorithm>
using namespace std;
struct zzx{
int x,y,z;
} a[10010];
int fa[10010];
bool cmp(zzx a,zzx b)
{
return a.z<b.z;
}
int father(int x)
{
if (x==fa[x]) return x;
fa[x]=father(fa[x]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a+1,a+1+m,cmp);
for (int i=1;i<=n;i++) fa[i]=i;
int s=0;
for (int i=1;i<=m;i++)
{
int fax=father(a[i].x);
int fay=father(a[i].y);
if (fax!=fay)
{
s=s+a[i].z;
fa[fax]=fay;
}
}
printf("%d\n",s);
}
LCA:
//fa表示每个点的父节点,deep表示每个点的深度
int fa[100],deep[100];
int LCA(int a,int b)
{
//在函数中确保a的深度大于b的深度,方便后面操作。
if(deep[a]<deep[b])
swap(a,b);
//让b不断地跳到他的父节点上,直到与a的深度相同
while(deep[a]>deep[b])
b=deep[b];
//让a和b同时往上跳,直到两者相遇。
while(a!=b)
{
a=deep[a];
b=deep[b];
}
return a;
}
倍增:
https://blog.csdn.net/Q_M_X_D_D_/article/details/89924963(有讲解)
//fa表示每个点的父节点
int fa[100],DP[100][20];
void init()
{
//n为结点数,先初始化DP数组
for(int i=1;i<=n;i++)
dp[i][0]=fa[i];
//动态规划求出整个DP数组
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
DP[i][j]=DP[DP[i][j-1]][j-1];
}
//查询函数
int LCA(int a,int b)
{
//确保a的深度大于b,便于后面操作。
if(dep[a]<dep[b])
swap(a,b);
//让a不断往上跳,直到与b处于同一深度
//若不能确保a的深度大于b,则在这一步中就无法确定往上跳的是a还是b
for(int i=19;i>=0;i--)
{
//往上跳就是深度减少的过程
if(dep[a]-(1<<i)>=dep[b])
a=dp[a][i];
}
//若二者处于同一深度后,正好相遇,则这个点就是LCA
if(a==b)
return a;
//a和b同时往上跳,从大到小遍历步长,遇到合适的就跳上去,不合适就减少步长
for(int i=19;i>=0;i--)
{
//若二者没相遇则跳上去
if(dp[a][i]!=dp[b][i])
{
a=dp[a][i];
b=dp[b][i];
}
}
//最后a和b跳到了LCA的下一层,LCA就是a和b的父节点
return dp[a][0];
}
差分:
序列差分:https://gmoj.net/junior/#main/show/2162
对于一个矩形,通过四个顶点位置的值的加减,实现矩形内所有位置的加减操作。单点查询时用前缀和。
var n,q,x,y,i,j,x1,y1,x2,y2:longint;
a:array[1..3001,1..3001]of longint;
sum:array[0..3000,0..3000]of longint;
begin
assign(input,'square.in');reset(input);
assign(output,'square.out');rewrite(output);
readln(n);
for i:=1 to n do
begin
readln(x1,y1,x2,y2);
inc(a[x1,y1]);
dec(a[x1,y2+1]);
dec(a[x2+1,y1]);
inc(a[x2+1,y2+1]);
end;
for i:=1 to 3000 do
for j:=1 to 3000 do
sum[i,j]:=sum[i-1,j]+sum[i,j-1]-sum[i-1,j-1]+a[i,j] ;
readln(q);
for i:=1 to q do
begin
readln(x,y);
writeln(sum[x,y]);
end;
close(input);close(output);
end.
树上差分:
树上两点x,y,使得x~y的路径上所有边+a。则把x的父边+a,y的父边+a,他们lca的父边-2a。查询一条边的值,通过查询该边的子树和来实现。
树上两点x,y,使得x~y的路径上所有点+a。则把x的值+a,y的值+a,他们lca的值-a,他们lca的父亲的值-a。查询一个点的值,通过查询它的子树和来实现。
二分图匹配(匈牙利算法):
https://blog.csdn.net/sunny_hun/article/details/80627351(有讲解)
#include<cstdio>
#include<cstring>
int a[1010][1010];
int f[1010];
bool bz[1010];
bool pd(int x)
{
for (int i=1;i<=a[x][0];i++)
if (!bz[a[x][i]])
{
bz[a[x][i]]=true;
if (f[a[x][i]]==0 || pd(f[a[x][i]]))
{
f[a[x][i]]=x;
return true;
}
}
return false;
}
int main()
{
int n,m,e;
scanf("%d%d%d",&n,&m,&e);
int x,y;
for (int i=1;i<=e;i++)
{
scanf("%d%d",&x,&y);
if (y>m) continue;
a[x][++a[x][0]]=y;
}
int ans=0;
for (int i=1;i<=n;i++)
{
memset(bz,0,sizeof(bz));
if (pd(i)) ans++;
}
printf("%d\n",ans);
return 0;
}
常见期望计算:
三个点,相邻两点间由双向边相连。
设f[i]表示从1号点出发,第一次到i号点的期望步数。
则f[1]=0,f[2]=1。现在求f[3]。
考虑一开始只能从1号点到2号点。在2号点时,可以回到1号,也可以去往3号,各为1/2的概率。若去往3号,则结束。若回到1号,则相当于重头开始。
利用方程思想:
f[3]=1/2*(f[2]+1)+1/2*(f[2]+1+f[3]),前半部分为去往3号点,后半部分为回到1号点重头开始。解得f[3]=4.
树上的期望计算也可以用类似的方程思想。
树链剖分:
https://blog.csdn.net/HiChocolate/article/details/77170675 (模板,搬运)
将树边分为轻边和重边。树上的每一层都有且只有一条重边,为所连向的儿子拥有最多后代(子树大小最大)的边。重边所连向的儿子称为重儿子。
树链剖分的过程为2次dfs
第一次:按照定义找出重边、重儿子
第二次:按照优先走重边的原则走出一个dfs序,点x在dfs序中的位置记为tree[x],并算出每个点所属重链的起点top[x]。
剖分完之后,每条重链就相当于一段区间,用数据结构(如线段树等)去维护。
把所有的重链首尾相接,放到同一个数据结构上,然后维护这一个整体即可。
如果u和v在同一条重链上,直接用数据结构修改tree[u]至tree[v]间的值或查询答案。
如果u和v不在同一条重链上,一边进行修改,一边将u和v往同一条重链上靠,然后就变成了上面的情况。
具体操作:我们把深度较大的一方x跳到他的fa[top[x]]上,并修改或查询tree[top[x]]~tree[x]
由于一条重链在数据结构中是一段连续的区间,所以直接查询tree[top[x]]~tree[x]是没问题的。
数论:
Gcd:
#include<cstdio>
int gcd(int x,int y)
{
if (x%y==0) return y; else return gcd(y,x%y);
}
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",gcd(a,b));
return 0;
}
快速幂:
#include<cstdio>
long long mi(long long a,long long b)
{
long long s=1;
while (b)
{
if (b&1) s=s*a;
a=a*a;
b/=2;
}
return s;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
long long s=mi(n,m);
printf("%lld\n",s);
return 0;
}
求某个数在%p意义下的逆元:
当p为质数时,一个数除以x就等于乘以x^(p-2)。以此实现模意义下得除法运算。
当p不为质数时,没有逆元,此时应避免出现除法运算。
质数筛法:
#include<cstdio>
bool bz[100010];
int zhi[100010];
int main()
{
int n;
scanf("%d",&n);
for (int i=3;i*i<=n;i+=2)
if (!bz[i])
for (int j=i;i*j<=n;j+=2) bz[i*j]=true; //注意bz并没有标记偶数
zhi[0]=1; zhi[1]=2;
for (int i=3;i<=n;i+=2)
if (!bz[i]) zhi[++zhi[0]]=i;
for (int i=1;i<=zhi[0];i++) printf("%d ",zhi[i]);
return 0;
}
Exgcd:
https://www.cnblogs.com/mrclr/p/9380300.html
exgcd可以用来求解方程ax +by = gcd(a, b)的一组x,y。
int exgcd(int a,int b,int &x,int &y) //注意x,y前的&
{
int s;
if (!b)
{
s=a,x=1,y=0;
return s;
}
s=exgcd(b,a%b,y,x); y-=a/b*x;
return s;
}
龟速乘:
我们有两个数a,b,要求a*b%p的结果。
如果a和b虽然都不超过long long,但乘在一起就超过了怎么办呢?
把a*b变成b个a相加,然后像快速幂那样,只是每次乘法变成了加法。
这样由于是一点一点加上去的,每次加都取模,所以就不会爆long long了
https://blog.csdn.net/jz_terry/article/details/86670193
long long cheng(long long a,long long b)
{
long long s=0;
while (b)
{
if (b&1) s=(s+a)%Mo;
a=(a+a)%Mo;
b>>=1;
}
return s;
}
线性求1~n在%p意义下的逆元:递推,f[i]=f[p%i]*(p-p/i)%p
推导:令p=a*i+b,则a*i+b 0(%p)。因为要求i的逆元,所以应将式子转化为i^-1=...的形式。即1/i=-a/b=-y/x*(y%x)^-1,为避免出现负数,还应加上p。所以得到上述递推式。
线性求1~n中每个数i的阶乘i!的逆元:f[i]=f[i+1]*(i+1)%p.
证明:i!^-1=(i+1)!^-1)(i+1) (乘上(i+1)是为了消掉(i+1)^-1,这样就可以由n倒着推来了)
DP:
背包问题dp:
01背包:
有t种物品和一个容量为n的背包。第i种物品只有1个,体积是v[i],价值是w[i]。选择物品装入背包使这些物品的体积总和不超过背包容量,且价值总和最大,求出这个最大价值。
var i,j,t,n:longint;
f:array[0..1000,0..1000]of longint;
w,v:array[1..1000]of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end;
begin
readln(n,t); //t是物品数,n是容量数
for i:=1 to t do
readln(w[i],v[i]);
for i:=1 to t do
for j:=1 to n do
if j>=w[i] then
f[i,j]:=max(f[i-1,j],f[i-1,j-w[i]]+v[i]) else
f[i,j]:=f[i-1,j];
writeln(f[t,n]);
end.
完全背包:
有t种物品和一个容量为n的背包。第i种物品有无穷个,体积是v[i],价值是w[i]。
选择物品装入背包使这些物品的体积总和不超过背包容量,且价值总和最大,求出这个最大价值。
var w,v:array[0..10000]of longint;
f:array[0..1000,0..10000]of longint;
i,j,n,m,k,l,t:longint;
function max(x,y:longint):longint;
begin
if x>y then exit(x) else exit(y);
end;
begin
readln(n,t);
for i:=1 to t do readln(w[i],v[i]);
for i:=1 to t do
begin
for j:=1 to n do
begin
for k:=0 to j div w[i] do
begin
if w[i]*k>j then f[i,j]:=f[i-1,j]
else f[i,j]:=max(f[i,j],f[i-1,j-w[i]*k]+v[i]*k)
end;
end;
end;
write(f[t,n]);
end.
最长不下降子序列的dp:
设b[i]表示最长不下降子序列的第i位最小能是多少(要满足第i位不小于第i-1位)
每次新加入一个数,如果能增长子序列则增长,增长不了就看看能不能更新b。
var n,i,j:longint;
a,b:array[0..100]of longint;
begin
readln(n);
for i:=1 to n do read(a[i]);
b[0]:=0;
for i:=1 to n do
begin
if a[i]>=b[b[0]] then
begin
inc(b[0]);
b[b[0]]:=a[i];
end else
begin
for j:=b[0]-1 downto 1 do //若这一部分改为二分,复杂度就是nlogn了。
if a[i]>=b[j] then
begin
b[j+1]:=a[i];
break;
end;
if b[1]>a[i] then b[1]:=a[i];
end;
end;
writeln(b[0]);
end.
树形dp:
一棵n个点的树,每个点有一个权值(可正可负),求树中权值和最大的子树,输出该最大的权值。
#include<cstdio>
int tot;
int nxt[2010],to[2010],las[1010];
int f[1010];
int a[1010];
void insert(int x,int y)
{
nxt[++tot]=las[x];
las[x]=tot;
to[tot]=y;
}
void dfs(int x,int fa)
{
f[x]=a[x];
for (int i=las[x];i;i=nxt[i])
if (to[i]!=fa)
{
dfs(to[i],x);
f[x]=f[x]+f[to[i]];
}
}
int main()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n-1;i++)
{
int x,y;
insert(x,y);
insert(y,x);
}
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
dfs(1,0);
int mx=0;
for (int i=1;i<=n;i++)
if (f[i]>mx) mx=f[i];
printf("%d\n",mx);
}
单调队列:
不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
https://blog.csdn.net/ljd201724114126/article/details/80663855
斜率优化:
https://blog.csdn.net/jz_terry/article/details/103212006
其它:
二分:(容易出锅,建议打完之后调试或是脑补模拟几遍)
一个从小到大排好序的序列,找出第一个大于x的数的位置。
#include<cstdio>
int a[1010];
int main()
{
int n,x;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d",&x);
int l=1; int r=n+1; //因为r表示的是满足条件的最小位置,所以初值不能为n(a[n]不一定大于x)
while (l<r)
{
int mid=(l+r)/2;
if (a[mid]<=x) l=mid+1; else r=mid;
}
printf("%d\n",r);
return 0;
}
一个从小到大排好序的序列,找出最后一个小于x的数的位置。
#include<cstdio>
int a[1010];
int main()
{
int n,x;
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d",&x);
int l=1; int r=n; //定义l-1是满足条件的,r+1是不满足条件的
while (l<=r) //与前面不同,l=r时也要做,判断l满不满足条件
{
int mid=(l+r)/2;
if (a[mid]>=x) r=mid-1; else l=mid+1;
}
printf("%d\n",l-1);
return 0;
}
线段树:
单点修改,区间查询:
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
int n,m,k,x,y,ans;
int a[100010];
int tree[400010];
void maketree(int x,int st,int en)
{
int m;
if (st==en) tree[x]=a[st];
else
{
m=(st+en)>>1;
maketree(x+x,st,m);
maketree(x+x+1,m+1,en);
tree[x]=max(tree[x+x],tree[x+x+1]);
}
}
void change(int x,int st,int en,int p,int value)
{
int m;
if (st==en) tree[x]=value;
else
{
m=(st+en)>>1;
if (p<=m)
{
change(x+x,st,m,p,value);
}
else change(x+x+1,m+1,en,p,value);
tree[x]=max(tree[x+x],tree[x+x+1]);
}
}
void find(int x,int st,int en,int l,int r)
{
int m;
if (l==st && r==en) ans=max(tree[x],ans);
else
{
m=(st+en)>>1;
if (r<=m) find(x+x,st,m,l,r);
else if (l>m) find(x+x+1,m+1,en,l,r);
else
{
find(x+x,st,m,l,m);
find(x+x+1,m+1,en,m+1,r);
}
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
maketree(1,1,n);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&k,&x,&y);
if (k==1)
{
change(1,1,n,x,y);
}
else if (k==2)
{
ans=0;
find(1,1,n,x,y);
printf("%d\n",ans);
}
}
}
区间修改,区间查询:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
long long f[300010];
int a[300010];
int g[300010];
long long ans;
int n,m;
int x,l,r,c;
void maketree(int x,int st,int en)
{
int mid;
if (st==en) f[x]=a[st];
else
{
mid=(st+en)/2;
maketree(x*2,st,mid);
maketree(x*2+1,mid+1,en);
f[x]=max(f[x*2],f[x*2+1]);
}
}
void update(int x,int st,int en,int l,int r,int v)
{
int mid;
if (st==l && en==r)
{
f[x]=f[x]+v;
g[x]=g[x]+v;
}
else
{
f[x*2]+=g[x];
f[x*2+1]+=g[x];
g[x*2]+=g[x];
g[x*2+1]+=g[x];
g[x]=0;
mid=(st+en)/2;
if (r<=mid) update(x*2,st,mid,l,r,v);
else if (l>mid) update(x*2+1,mid+1,en,l,r,v);
else
{
update(x*2,st,mid,l,mid,v);
update(x*2+1,mid+1,en,mid+1,r,v);
}
f[x]=max(f[x*2],f[x*2+1]);
}
}
void getmax(int x,int st,int en,int l,int r)
{
int mid;
if (st==l && en==r)
{
ans=max(ans,f[x]);
}
else
{
//
f[x*2]+=g[x];
f[x*2+1]+=g[x];
g[x*2]+=g[x];
g[x*2+1]+=g[x];
g[x]=0;
//
mid=(st+en)/2;
if (r<=mid) getmax(x*2,st,mid,l,r);
else if (l>mid) getmax(x*2+1,mid+1,en,l,r);
else
{
getmax(x*2,st,mid,l,mid);
getmax(x*2+1,mid+1,en,mid+1,r);
}
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
maketree(1,1,n);
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d",&x);
if (x==1)
{
scanf("%d%d%d",&l,&r,&c);
update(1,1,n,l,r,c);
}
else
{
ans=-2147483647000;
scanf("%d%d",&l,&r);
getmax(1,1,n,l,r);
printf("%d\n",ans);
}
}
//system("pause");
return 0;
}
树状数组:
https://blog.csdn.net/jz_terry/article/details/78543825
记得two[i]=i&(-i);
单点修改,区间查询
#include<cstdio>
int a[100001];
int two[100001];
int c[100001];
int sum[100001];
char ch;
int qiuhe(int x)
{
int s=0;
while (x>0)
{
s+=c[x];
x=x-two[x];
}
return s;
}
int main()
{
int n,m;
scanf("%d",&n);
int i;
for (i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for (i=1;i<=n;i++) two[i]=i&(-i);
for (i=1;i<=n;i++)
c[i]=sum[i]-sum[i-two[i]];
scanf("%d",&m);
int x,y,k,t;
for (i=1;i<=m;i++)
{
scanf("\n%c%d%d",&ch,&x,&y);
if (ch=='C')
{
k=x;
t=a[x];
a[x]=y;
while (k<=n)
{
c[k]=c[k]-t+y;
k=k+two[k];
}
} else
{
printf("%d\n",qiuhe(y)-qiuhe(x-1));
}
}
return 0;
}
KMP:
#include<cstdio>
char a[100001];
char b[100001];
int p[100001];
int f[100001];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
scanf("%s",a+1);
scanf("%s",b+1);
int i,j;
for (i=2;i<=m;i++)
{
j=p[i-1]; //p[i]表示匹配串中,以第i位结尾的一个后缀,即i-p[i]+1~i,与1~p[i]相同。要使得p[i]在满足上述条件下最大。
while (j>0 && b[j+1]!=b[i]) j=p[j]; //先沿用p[i-1],然后因为要多满足1位,所以不断失配,不断调整缩减。
if (b[j+1]==b[i]) p[i]=j+1; //找到合法的就更新,没找到p[i]就为0
}
for (i=1;i<=n;i++)
{
j=f[i-1];//f[i]表示原串中第i位为后缀,原串的i-f[i]+1~i与匹配串的1~f[i]相同,最大化f[i]。
while (j>0 && b[j+1]!=a[i]) j=p[j]; 先沿用f[i-1],然后因为要多满足1位,所以不断失配,不断调整缩减。
if (b[j+1]==a[i])
{
f[i]=j+1;
if (f[i]==m)
{
printf("%d\n",i-m+1);
f[i]=p[f[i]]; //找到一个完整匹配后,为了继续找下一个,f[i]要回跳一步。
}
}
}
return 0;
}
最小堆:
https://blog.csdn.net/hrn1216/article/details/51465270(如果你忘了原理的话)
堆排序
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
int g[300010],i,n,a;
void up(int t)
{
int q;
if(t==1)return;
q=t/2;
if(g[q]>g[t])
{
a=g[q]; g[q]=g[t]; g[t]=a;
up(q);
}
}
void down(int t)
{
int p;
if(t*2>n)return;
p=t*2;
if((p<n)&&(g[p+1]<g[p]))p++;
if(g[p]<g[t])
{
a=g[p]; g[p]=g[t]; g[t]=a;
down(p);
}
}
int main()
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&g[i]);
up(i);
}
for(;n>1;)
{
printf("%d\n",g[1]);
g[1]=g[n];
n--;
down(1);
}
printf("%d\n",g[1]);
}
哈希:
通过对每一位乘上不同系数并取模的方式,将一个字符串/一个很大的数,转化为一个比较小的数(相当于赋予其一个编号),并尝试存入数组中下标为该编号的位置。若数组中该编号下标已被存放,且原串不同,则将该编号逐步累加,直到找到一个空位插入。
马拉车:
https://blog.csdn.net/Gao_Jue_Yi/article/details/81435328(你以前的博客)
#include<cstdio>
#include<cstring>
char a[11000010];
char b[22000010];
int p[22000010];
int main()
{
scanf("%s",a+1);
int n=strlen(a+1);
b[1]='*';
int m=1;
for (int i=1;i<=n;i++)
b[++m]=a[i],b[++m]='*';
int x=0; int y=0;
int ans=0;
for (int i=1;i<=m;i++)
{
if (y>i)
{
if (p[x*2-i]+i-1<=y) p[i]=p[x*2-i]; else p[i]=y-i+1;
} else p[i]=1;
while (i-p[i]>=1 && i+p[i]<=m && b[i-p[i]]==b[i+p[i]]) p[i]++;
if (y<i+p[i]-1)
{
y=i+p[i]-1;
x=i;
}
if (p[i]-1>ans) ans=p[i]-1;
}
printf("%d\n",ans);
return 0;
}
网络流:(应该不属于联赛范围,不要求掌握)
#include<cstdio>
#include<cstring>
int n,m,S,T;
int las[10010],nxt[200010],to[200010];
long long num[200010];
int gap[10010],dis[10010];
int tot=-1;
int min(long long x,long long y)
{
if (x<y) return x; else return y;
}
void insert(int x,int y,long long z)
{
nxt[++tot]=las[x];
las[x]=tot;
to[tot]=y;
num[tot]=z;
}
long long dfs(int x,long long s)
{
if (x==T) return s; //已经流到汇点了,返回流量
int have=0; //已经流了多少流量到汇点T
for (int i=las[x];i!=-1;i=nxt[i])
if (dis[to[i]]+1==dis[x] && num[i]>0) //距离标号,增加流量要满足最短路性质,且这条边有值
{
int now=dfs(to[i],min(s-have,num[i]));
num[i]-=now; num[i^1]+=now; //i^1是to[i]->x的反向边编号,通过存边时实现
have+=now; //更新存储流量
if (have==s) return s; //如果已经满流了,就直接返回,防止距离标号被不可能的流改变
}
if (--gap[dis[x]]==0) dis[1]=n; //若距离标号存在断层,则不可能再流了
gap[++dis[x]]++;//更新距离标号
return have;
}
int main()
{
scanf("%d%d%d%d",&n,&m,&S,&T);
memset(las,255,sizeof(las));
memset(nxt,255,sizeof(nxt));
for (int i=1;i<=m;i++)
{
int x,y; long long z;
scanf("%d%d%lld",&x,&y,&z);
insert(x,y,z);
insert(y,x,0);
}
long long sum=0;
gap[0]=n;
while (dis[S]<n) sum+=dfs(S,9000000000000000LL);
printf("%lld\n",sum);
return 0;
}
套路&技巧&注意事项:
- 求[l..r]方案数的一般都是转化为[1..r]-[1..l-1]。
- 涉及区间覆盖的,想一想差分。
- 求最小最大时想二分
4、不能的个数=总数-能的个数。
5、c++很少会报错,所以要自己注意数组是否会越界,要不要开long long。
6、打二分时要多调试几遍。
7、注意时间分配,想不到就打暴力。
8、一些看上去超时的搜索在加上剪枝后能拿到很多分甚至能切掉,所以搜索要尽量打剪枝,比如最大最小值剪枝、合法性剪枝、记忆化等。
9、注意数据范围内那些奇怪的特殊数据。
10、c++中位运算的优先级很低,所以最好加括号。
11、做题要多看几遍题目,想做法时样例要先看懂。
12、有些要取模的题在卡常时可以隔几个去一次模,因为取模很慢。
13、调用数组也比较慢,至少比直接用变量慢,所以卡常时能用变量就别调用数组。
14、记得初始化。
15、在空间允许的情况下,不要吝啬开long long耗费的空间,感觉数值可能比较大就开long long。
16、遇到实数类型就直接用double(pascal的extended),以防被卡精度。
17、线段树空间要开4n。也就是说100000个数要开到400000的空间来存。
18、数据很大好像只能O(1)过的一般都是公式结论题,推不出公式可以打个暴力来找规律。
19、sqrt一定要开cmath。虽然在编译器上只开algorithm也可以通过,但交上去会错。
20、不要用get、last、next等英文全拼的变量名,开了cmath库时不要用x0,y0,x1,y1之类。因为不同编译环境下可能会编译错误。