各种总结 [各种妙啊]
[by_2018年的041]
解题思想
暴力
<这个就不附代码了>
模拟搜索
后台打表
递推递归
搜索最优解
- 贪心出一解,and暴搜 😆
取巧
二分答案
- 在可能存在答案的单调序数列上的 Θ ( l o g N ) \Theta(logN) Θ(logN)查找
int l,r,mid;
/*[l,r]的初始化
为答案可能出现的两端*/
while(l<r)
{
mid=(l+r)/2;
if(f(mid)>=goal)
l=mid;
else
r=mid+1;
}
// 结果 : l==r 都为目标地址
区间分治
- 将问题划分成几个部分处理再合并产生答案, Θ ( d N ) , ( d 是 深 度 常 数 ) \Theta(dN),(d是深度常数) Θ(dN),(d是深度常数)
记忆动归
- 在各个参数范围允许的情况下,函数的每一次返回值都可以被保存,并且高效率协助剪枝优化,时间复杂度近似 Θ ( N ) \Theta(N) Θ(N)
倍增法求区间最值[RMQ]
-
开一数组
f[i][j]
,用于存储区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]中的 最大(小)值 -
初始化
f[i][0]=value[i];
-
递推式:
f[i][j]=max(f[i][j-1],f[i+(1<<j-1)-1][j-1]);
-
代码实现 : (当时年少轻狂的头文件呀快速读写呀)
#include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define N_ 10007 #define Log_2_N_ 107 int n,m,i,j,endi,endj,log_2[Log_2_N_],a[N_],f[N_][Log_2_N_]; inline void input(int&a) { register char ch;register bool pos=true; while((ch=getchar())<'0'||ch>'9') if(ch=='-') pos=false; a=ch-'0'; while((ch=getchar())>='0'&&ch<='9') a=(a<<1)+(a<<3)+ch-'0'; if(!pos) a=-a; return; } inline void output(int a) { if(!a) { putchar('0'); return; } register int f=0; register char s[12],sig='\0'; if(a<0) { sig='-'; a=-a; } while(a) { s[f++]=a%10+'0'; a/=10; } if(sig) putchar(sig); while(f--) putchar(s[f]); return; } ///前面不看 int main() { for(input(n),input(m);i<n;f[i][0]=a[i++]) input(a[i]); endj=ceil(log(n)/log(2)); for(j=1;j<=endj;j++) { log_2[j]=floor(log(j)/log(2)); endi=n-(1<<j); for(i=0;i<=endi;i++) f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);//重点!!!!!!! } do log_2[j]=floor(log(j)/log(2)); while(++j<=n); while(m--) { input(i);input(j);i--;j--; output(max(f[i][log_2[j-i+1]],f[j+1-(1<<log_2[j-i+1])][log_2[j-i+1]])); putchar('\n'); } return 0; }
数组/结构体 模拟各种数据结构
- 队列
queue
- 栈
stack
- 堆
map
还是heap
来着 - 链表
chain
☆ 效率太慢 ? ? 开数组模拟吧 !
算法部分
代数算法
求 a ∗ b ( m o d m ) a*b(modm) a∗b(modm)的O(1)算法
'妙啊’系数 ⋆ ⋆ ⋆ ⋆ \star\star\star\star ⋆⋆⋆⋆
template<typename T> // 一般是int,long long这类
inline T multiply(T a,T b,T m)
{
T c=a*b-(T)((long double)a*b/m+0.5)*m;
return c<0?c+m:c; // ※归正※
}
位运算
-
膜二的k次幂
(n&0x7ff)
(以k=10为例,其中`0x7ff`可换作(1<<k)-1) -
求a的二进制中有几个
1
inline int count(int a) { int c=0; while(a) { c++; a&=a-1; } return c; }
-
待添加
数论
乘法原理
质数表
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define N_ 100001
int n,i,j,endi,pf,prime[N_]={};
bool isnp[N_]={1,1};
void output(int a)
{
if(a>9)
{
output(a/10);
a%=10;
}
putchar(a+'0');
return;
}
int main()
{
for(scanf("%i",&n),endi=sqrt(n),i=2;i<=endi;i++)
{
if(!isnp[i])
prime[++pf]=i;
j=pf;
while(prime[j]*i>n)j--;
while(j)
isnp[prime[j--]*i]=true;
}
while(i<=n)
{
if(!isnp[i])
prime[++pf]=i;
j=pf;
while(prime[j]*i>n)j--;
while(j)
isnp[prime[j--]*i]=true;
i++;
}
for(i=1;i<=pf;i++,putchar('\n'))
output(prime[i]);
return 0;
}
因数个数表
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
#define N_ 100001
int n,i,j,endi,endj,pf,prime[N_],isnp[N_],factor[N_]={0,1};
void output(int a)
{
if(a>9)
{
output(a/10);
a%=10;
}
putchar(a+'0');
return;
}
int main()
{
for(scanf("%i",&n),endi=sqrt(n),i=2;i<=endi;i++)
{
factor[i]+=2;
if(!isnp[i])
prime[++pf]=i;
for(j=i<<1;j<=n;j+=i)
{
isnp[j]=true;
factor[j]++;
}
}
while(i<=n)
{
factor[i]+=2;
if(!isnp[i])
prime[++pf]=i;
for(j=i<<1;j<=n;j+=i)
{
isnp[j]=true;
factor[j]++;
}
i++;
}
for(i=1;i<=n;i++,putchar('\n'))
{
output(i);
putchar('\t');
output(factor[i]);
}
return 0;
}
求最大公约数(gcd)
- 由辗转相除法得 :
inline int gcd(int a,int b)
{
while(a)
{
a^=b;
b^=a;
a^=b;
a%=b;
}
return b;
}
求最大公倍数(lcm)
- ∵a×b=lcm(a,b)×gcd(a,b) ∴ \therefore ∴ lcm(a,b)=a*b/gcd(a,b);
inline int gcd(int a,int b)
{
while(a)
{
a^=b;
b^=a;
a^=b;
a%=b;
}
return b;
}
inline int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
图论实践 [还需训练]
关于图的几个概念定义
- 连通图:在无向图中,若任意两个顶点与都有路径相通,则称该无向图为连通图。
- 强连通图:在有向图中,若任意两个顶点与都有路径相通,则称该有向图为强连通图。
- 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
- 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
- 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
图的存储结构
|[参考网址1][https://blog.csdn.net/qq_22238021/article/details/78281939]| |[参考网址2][https://blog.csdn.net/EbowTang/article/details/44163561]| |[参考网址3][https://blog.csdn.net/qiu931110/article/details/80429589]|
邻接矩阵
a[i][j]表示节点i到节点j的距离
a[i↓][j→] | 0 | 1 | 2 | … | n-1 |
---|---|---|---|---|---|
0 | 0 | d[0][1] | d[0][2] | … | d[0][n-1] |
1 | d[1][0] | 0 | d[1][2] | … | d[1][n-1] |
2 | d[2][0] | d[2][1] | 0 | … | d[2][n-1] |
… | … | … | … | … | … |
n-1 | d[n-1][0] | d[n-1][1] | d[n-1][2] | … | 0 |
边数组
p[i]表示第i条边,其成员from表示起点,to表示终点,value表示边权值
struct Pass_
{int from,to,value;}
p[N_];
邻接类表
邻接表[记入度]/逆邻接表[记出度]/十字链表[前两种之和]/邻接多重表[无向边关联记录]
- 二维数组状(eg.邻接表)
flowin[i]表示i节点的入,to[i][j]表示从i节点出发的记录编号为j的边
p[i↓] | flowin | to [j→0] | 1 | … | n-1 |
---|---|---|---|---|---|
0 | 1 | 1 | NULL | … | NULL |
1 | 0 | NULL | NULL | … | NULL |
2 | 5 | 1 | 3 | … | NULL |
… | … | … | … | … | … |
n-1 | 3 | 1 | 2 | … | NULL |
-
链式(eg.逆邻接表)
last[i]表示最后记录到的以第i个节点为起点的边编号
p[i]表示编号为i的边,其成员to表示该边的终点,next为上一条与该边拥有共同起点的边的编号,value表示该点边权
last[i] | p[i↓] | to | next | value |
---|---|---|---|---|
1 | 0 | 1 | NULL | v[0][1] |
n-11 | 1 | 3 | 0 | v[0][3] |
1 | 2 | 3 | NULL | v[1][3] |
… | … | … | … | … |
m-11 | n-1 | 14 | 2 | v[1][14] |
… | … | … | … | … |
NULL | m-1 | 4 | 18 | v[n-1][4] |
-
十字链表[即是邻接表和逆邻接表的结合]
为同时存储邻接表和逆邻接表,所以有两个指针位[其余参考上面两种结构]
-
邻接多重表[无向]
加一个指针位指向与本边等位的边的编号2
相关考点
|[参考网址1][https://www.cnblogs.com/chuninggao/p/7301083.html]| |[参考网址2][https://blog.csdn.net/zezzezzez/article/details/70245548]| |[参考网址3][https://blog.csdn.net/qq_22238021/article/details/79489256]|
最短路
-
Dijkstra[使用邻接矩阵的时间复杂度为O(n^2),用优先队列的复杂度为O((m+n)logn)近似为O(mlogn)]
- 单源最短路算法,即计算从起点出发到每个点的最短路,不能出现负权
- 每次选择一个未访问过的到已经访问过的所有点的集合的最短边
- 可用于记录路径
-
SPFA[又称bellman-ford,O(kE)E为边数,k一般为2或3]
-
dis[n][u]是从s(源点)到u的只经过n条边的最短路径长度
-
当图G中没有从源可达的负权图时,从s到u的最短路径上最多有n-1条边。因此,
dist(n-1)[u]就是从s到u的最短路径长度,显然,若从源s到u的边长为e(s,u),则dis1[u]=e(s,u).对于k>1,dis(k)[u]满足如下递归式,dis(k)[u]=min{dis(k-1)[v]+e(v,u)}.
-
实现如下:用数组dis记录更新后的状态,cnt记录更新的次数,队列q记录更新过的顶点,算法依次从q中取出待更新的顶点v,按照dis(k)[u]的递归式计算。在计算过程中,一旦发现顶点K有cnt[k]>n,说明有一个从顶点K出发的负权圈,此时没有最短路,应终止算法。否则,队列为空的时候,算法得到G的各顶点的最短路径长度。
-
优化
SLF(Small Label First)是指在入队时如果当前点的dist值小于队首, 则插入到队首, 否则插入到队尾。
-
应用:
眼见的同学应该发现了,上面的[差分约束][https://baike.baidu.com/item/差分约束系统/5182920]四个字,是的SPFA可以很好的实现差分约束系统。
|[差分约束系统参考网站][https://blog.csdn.net/consciousman/article/details/53812818]|
-
-
Floyd[全称Floyd-Warshall,O(n^3)]
-
DP : dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
即从顶点i到j且经过顶点k的最短路径长度。
-
可以解最小环问题
-
-
BFS[宽度优先搜索]
- 如走迷宫经常用的,以一个点出发,向外扩散更新.[^个人经常用]
-
最小生成树
-
Kruskal算法
-
排序查找最小权贡献边[将不联通的两点连起]
#include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; #define N_ 10001 #define M_ 100001 int n,m,i,anss,dot[N_]; inline void input(int&a) { char ch; while((ch=getchar())<'0'||ch>'9'); a=ch-'0'; while((ch=getchar())>='0'&&ch<='9') a=(a<<1)+(a<<3)+ch-'0'; return; } void output(int a) { if(a>9) {output(a/10);a%=10;} putchar(a+'0'); return; } int set(int a) {return dot[a]==a?a:dot[a]=set(dot[a]);} struct Pass_ {int u,v,d; inline void in() {input(u);input(v);input(d);} #ifdef DEBUG inline void out() {output(u);putchar('\t'); output(v);putchar('\t'); output(d);putchar('\n');} #endif }p[M_]; inline bool cmp_p(Pass_ a,Pass_ b) {return a.d<b.d;} int main() { for(input(n),input(m);i<m;i++) p[i].in(); for(i=1;i<=n;i++) dot[i]=i; sort(p,p+m,cmp_p); for(i=0;i<m;i++) if(set(p[i].u)!=set(p[i].v)) { dot[set(p[i].u)]=set(p[i].v); anss+=p[i].d; } m=set(1); for(i=2;i<=n;i++) if(set(i)!=m) { puts("-1"); return 0; } output(anss); return 0; }
-
-
Prim算法[Dijkstra思想]
- 查找未加入的最小代价点,加入一(这)个点就用这个点更新一下它可以到达的点
-
二分图判断
- 代码:
#include<iostream> #include<vector> #include<cstdio> #include<cstring> using namespace std; int n,m; int vis[10001]; vector<int> v[10001]; bool dfs(int x) { vector<int>::iterator it; for(it=v[x].begin();it!=v[x].end();it++) { if(vis[*it]==vis[x])return false; if(vis[*it]==0) {vis[*it]=-vis[x]; if(!dfs(*it))return false; } } return true; } int main() { int t; cin>>t; while(t--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { v[i].clear(); } while(m--) { int a,b; scanf("%d%d",&a,&b); v[a].push_back(b); v[b].push_back(a); } memset(vis,0,sizeof(vis)); int k=0; for(int i=1;i<=n;i++) { if(vis[i]==0) { vis[i]=1; if(!dfs(i)) { k=1; cout<<"Wrong"<<endl; break; } } } if(!k)cout<<"Correct"<<endl; } return 0; } ```
- 代码:
-
-
缩点
是个啥
结构判断
- 链
- 环
- 连通图
- b a la b a la . . .
并查集
1.记录祖先
int father[N_]; // father[i]记录i节点的根节点(也可以表示为所属子集编号) 后来个人更喜欢用set[];
2.一步到位的神犇代码
int belongs(int fla) // 合并查找一步到位
{return father[fla]==fla?fla:father[fla]=find(father[fla]);}
// 合并节点x,y只需把father[belongs(x)]=father[y]即可
3.便于理解的模板
int belongs(int i) // 返回i节点的根节点顺便更新路径上的点
{
// 找根节点
register int fa=i;
while(father[fa]!=fa)
fa=father[fa];
// 更新路径上的点
register int fla=i,temp;
while(father[fla]!=fa)
{
temp=father[fla];
father[fla]=fa;
fla=temp;
}
return fa;
}
void combine(int x,int y) // 合并节点x,y所在的集合
{
register int fx=belongs(x),fy=belongs(y);
if(fx!=fy)
father[x]=fy;
} // 实际的合并并未完成,具体查找还需belongs跑一遍
几何
凸包
- 斜率优化< 暂定 : 详见2018夏令营内容 >
面积
- 凸包减内凹3
细节问题
- 根据需求选择变量类型 : 整形 , 正负 , 浮点 , 精度 . . . . . .
字符串处理
KSM法
- 建立树状数组[个人用邻接表]
搜索问题
搜索包含所有元素的最短区间[左右指针O(l)算法]
- 先固定左指针(l)于第一个有效元素的位置
- 右指针®从左指针右移一位(l+1)开始动态移动
- 当右指针®移到与左指针(l)上相同的元素位置时,将左指针(l)移到下一个有效元素的位置
- 在记录过程中最短长度(min(r-l)),此长度即包含所有元素的最短区间长度
- 可在更新长度时记录该区间的其他信息
模板
快速输入输出
- 长乐一中神犇的代码(数据只能是正整数)
inline int get(){ // 输入一个正整数
char ch;
while ((ch = getchar()) < '0' || ch > '9'); // 过滤非数字字符
int res = ch - '0';
while ((ch = getchar()) >= '0' && ch <= '9')
res = (res << 1) + (res << 3) + ch - '0';
return res;
}
inline void put(int x){ // 输出一个正整数
if (x > 9){ // 因为个位可能为0所以最后输出
put(x / 10); // ※递归操作,顺序输出※
x %= 10;
}
putchar(x + '0');
return;
}
// 基于字符(串)输入输出
- 自己的码(可以处理符号位)
inline int input(){
char ch;int a;bool neg=false;
while((ch=getchar())<'0'||ch>'9')
neg=ch=='-';
a=ch-'0';
while((ch=getchar())>='0'&&ch<='9')
a=(a<<1)+(a<<3)+ch-'0';
return neg?-a:a;
}
inline void output(int a){
if(a<0){putchar('-');a=-a;}
if(a>9){output(a/10);a%=10;}
putchar(a+'0');
return;
}
结构体相关
struct point{ // 以下注释都是猜测,未证实慎用
int x, y;
point (){} // 维护point u;
point (int _x, int _y): // 维护类型转换point(x,y);
x(_x), y(_y) {}
} ;
细节
大数据取模
- 先加膜数再膜,避免出现负数
ans[i]=(f(i)%mod+mod)%mod
分块处理
- 注意 数据量比处理量要少的情况
二分下标
- 左右下标(l,r)分别表示查找点可能存在的区域
[l,r],
把我这一性质进行不重不漏的二分实现规划
特判临界
- 等于不等于,包含不包含的问题 要考虑清楚 理清逻辑
- 指针区间无解,或解在特殊位置上(如:首尾)时 特判最好跟上
- 数据在0(或其他特殊值)附近的处理
运算符顺序
-
=
: 先左后右 -
位运算符[
<<
>>
|
&
等]的优先级很低(靠后),联合其他符号试用需谨慎[加()
][ [另附对照表][C:\Users\Teloy_041\Desktop#学习\Cpp#题目\FZBZOI#高二上日常#算法部分\模块笔记# 运算符优先级[表].pdf] ]
尚未查明
- 堆<heap>
- 缩点
[一个讲差分约束的博文][https://blog.csdn.net/consciousman/article/details/53812818]