1°FLOYD判最小环
注:包含基本的FLOYD算法。最好写的单源最短路径算法。但时间复杂度O(n^3),n=1000基本就用不了了。。。
void floyd(){
int MinCost = inf;
for(int k=1;k<=n;k++){
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
MinCost = min(MinCost,dis[i][j]+mp[i][k]+mp[k][j]);//更新k点之前枚举ij求经过ijk的最小环
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); //更新k点(FLOYD)
}
if(MinCost==inf)puts("It's impossible.");
else printf("%d\n",MinCost);
}
2°SPFA算法:单源最短路径算法
基本思路:将起点放入队列,每次取出队首元素,对其所有邻边进行松弛操作,并放入队列(若队列里已有该元素则无需重复添加,但若此时队列里没有该元素即使先前已进行过计算仍需放入队列不断更新)
优点:速度快,可处理权值为负的图
缺点:可能会被一些恶心的数据卡掉。。。
void spfa(int s){
for(int i=0; i<=n; i++) dis[i]=99999999; //初始化每点i到s的距离
dis[s]=0; vis[s]=1; q[1]=s; 队列初始化,s为起点
int i, v, head=0, tail=1;
while (head<tail){ 队列非空
head++;
v=q[head]; 取队首元素
vis[v]=0; 释放队首结点,因为这节点可能下次用来松弛其它节点,重新入队
for(i=0; i<=n; i++) 对所有顶点
if (a[v][i]>0 && dis[i]>dis[v]+a[v][i]){
dis[i] = dis[v]+a[v][i]; 修改最短路
if (vis[i]==0){ 如果扩展结点i不在队列中,入队
tail++;
q[tail]=i;
vis[i]=1;
}
}
}
}
3°Dijkstra(迪杰斯特拉)算法:单源最短路径算法
基本思路:每次确定到起点距离最短且距离尚未确定的点的dis,并对其所有邻边进行松弛操作
优点:非常常用!不容易被卡
缺点:慢(但相比FLOYD还是快的(雾
不加优化O(n^2)
堆优化(priority_queue或set) O(n log n)
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=1;j<=N;j++) if(!vis[j]) dist[j]=min(dist[j],dist[mark]+g[mark][j]);
}
}
4°匈牙利算法:二分图匹配(也可以用最大流求解,但这个似乎更好写一点)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=505;
int line[N][N];
int girl[N],used[N];
int k,m,n;
bool found(int x)
{
for(int i=1; i<=n; i++)
{
if(line[x][i]&&!used[i])
{
used[i]=1;
if(girl[i]==0||found(girl[i]))
{
girl[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{
int x,y;
while(scanf("%d",&k)&&k)
{
scanf("%d %d",&m,&n);
memset(line,0,sizeof(line));
memset(girl,0,sizeof(girl));
for(int i=0; i<k; i++)
{
scanf("%d %d",&x,&y);
line[x][y]=1;
}
int sum=0;
for(int i=1; i<=m; i++)
{
memset(used,0,sizeof(used));
if(found(i)) sum++;
}
printf("%d\n",sum);
}
return 0;
}
*转自博客算法讲解:二分图匹配 强推一波,讲解浅显易懂而且十分有趣233
5°最大流EK算法
这个是自己手打的啊。。。码风实在不堪入目
bool flag=true;
while(flag){
flag=false;
queue<int> q;
memset(vis,false,sizeof(vis));
q.push(0);
vis[0]=true;
memset(p,-1,sizeof(p));
while (!q.empty()) {
int node=q.front();
q.pop();
if (node==n) {
flag=true;
break;
}
for (int i=1;i<=n;i++) if (!vis[i]&&c[node][i]>f[node][i]) {
q.push(i);
vis[i]=true;
p[i]=node;
}
}
if (!flag) break;
int tmp=1e+7;
for (int i=n;p[i]!=-1;i=p[i])
tmp=min(tmp,c[p[i]][i]-f[p[i]][i]);
for (int i=n;p[i]!=-1;i=p[i]) {
f[p[i]][i]+=tmp;
c[i][p[i]]+=tmp;
}
}
int ans=0;
for (int i=1;i<=n+m+1;i++) ans+=f[0][i];
6° 单调队列&单调栈
单调队列:k个数中的最大/小值
单调栈:第k个数左/右最大/小的数 以及一些向左右拓展问题(POJ 2559矩形面积)
单调栈代码(手写deque)(单调队列差不多 只需要在q开头元素的长度已经超过k时qt++)
int qt=0,qw=-1;
void insert(int x, int val)
{
while (qt <= qw)
{
if (val > q[qw].val) break;
qw--;
}
qw++;
q[qw].val = val;
q[qw].x = x;
}
7°对从q[i]开始的k个数各加上a[i]
int cur=0;
for (int i=0;i<n;i++) {
tmp[i]+=a[i];
tmp[i+k-1]-=a[i];
q[i]+=a[i]+cur;
cur+=tmp[i];
}
8°dfs判环+拓扑序 O(V+E)
bool dfs(int cur){
if (vis[cur]==2) return true;
if (vis[cur]==1) return false;
vis[cur]=1;
for (int i=0;i<edge[cur].size();i++) if (!dfs(edge[cur][i])) return false;
vis[cur]=2;
p.push_back(cur);//拓扑序
return true;
}
9°拓扑排序(类比上一个)
若排序完成后仍有未被访问的点(或可理解为再循环结束前提前return)即代表图上有环
void solve(){
for (int i=0;i<node.size();i++) {
int j;
for (j=0;j<node.size();j++)
if (!vis[node[j]]&°[node[j]]==0) {
vis[node[j]]=true;
p.push_back(node[j]);
for (int k=0;k<edge[node[j]].size();k++)
if (!vis[edge[node[j]][k]])
deg[edge[node[j]][k]]--;
break;//勿忘!
}
if (j==node.size()) return;
}
return;
}
10° 假如有一个递增的队列 每次取出最小的一个 每次操作会产生一些新的量 且这些量要参与比较且也保证递增 那么用优先队列实现需要n log n 但我们可以只用两个队列分别存储 时间复杂度降到O(n)
下面给出的两个程序都是求哈弗曼编码长度的(且输入保证频数递增)
转自https://blog.youkuaiyun.com/blessLZH0108/article/details/70275161?locationNum=1&fps=1
//优先队列
#include<stdio.h>
#include<queue>
using namespace std;
typedef long long LL;
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
priority_queue<LL,vector<LL>,greater<LL> >q;
scanf("%d",&n);
LL ans=0,x,y;
for(int i=1; i<=n; ++i)
{
scanf("%lld",&x);
q.push(x);
}
while(!q.empty())
{
x=q.top();
q.pop();
if(q.empty())
break;
y=q.top();
q.pop();
ans+=x+y;
q.push(x+y);
}
printf("%lld\n",ans);
if(t)
puts("");
}
return 0;
}
//手写队列
#include<stdio.h>
#define maxn 500010
typedef long long LL;
const LL inf=100000000000;
LL a[maxn],b[maxn];
int head1,tail1,head2,tail2;
LL getmin()
{
LL x=inf,y=inf;
if(head1<tail1)
x=a[head1];
if(head2<tail2)
y=b[head2];
if(x<y)
{
++head1;
return x;
}
else
{
++head2;
return y;
}
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]);
head1=1,tail1=n+1,head2=1,tail2=1;
LL ans=0,x,y;
for(int i=1; i<=n-1; ++i)
{
x=getmin();
y=getmin();
ans+=x+y;
b[tail2++]=x+y;
}
printf("%lld\n",ans);
if(t)
puts("");
}
return 0;
}
update:后来学习了归并排序后才知道这其实是归并的思想233333
11°
配合二进制枚举子集使用 可以得出枚举的子集的子集(如11101可以得到保证第2位一直是0 这是和普通直接小于枚举的不同之处)
for(LL i=1;i<1<<n;i++)
for(LL j=i;j;j=(j-1)&i)
12°
矩阵中从点i到点j的方案数为C(|xi−xj|,|xi−xj|+|yi−yj|)