To be continued…
最短路算法
dijkstra算法
适用范围:无负权边
时间复杂度:
O(N2)
O
(
N
2
)
(无优化时)
然而…优先队列优化的dijkstra的均摊时间复杂度是多少?
斐波那契堆优化的dijkstra的复杂度(大约)是
O(MlogN)
O
(
M
l
o
g
N
)
,尽管优先队列优化没有堆优化那么强,但姑且当做这么多吧
无优化的dijkstra:(适用于稠密图)
void dijkstra(int s){
int x=1;
vis[s]=1;
while(x<n){
int mi=1<<30,xi;
for(int i=1;i<=n;i++)
if(vis[i])
for(int j=head[i];j;j=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(!vis[v]){
if(d[v]>d[u]+w){
d[v]=d[u]+w;
pre[v]=u;//输出路径用
if(d[v]<mi)
mi=d[v],xi=v;
}
}
}
vis[xi]=1;
x++;
}
}
优先队列优化的dijkstra:适用于稀疏图
void dijkstra(int s){
q.push(make_pair(0,s));
while(!q.empty()){
int u=q.front().first,val=q.front().second;
q.pop();
if(val>dist[u])
continue ;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
q.push(make_pair(dist[v],v));
}
}
}
}
SPFA算法
使用范围:基本所有
时间复杂度证明极其有毒,姑且看作跟优先队列优化的dijkstra一个数量级吧
void SPFA(int s){
q.push(s);
inqueue[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
inqueue[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(dist[v]>dist[u]+dist[w]){
dist[v]=dist[u]+dist[w];
if(!inqueue[v]){
inqueue[v]=1;
q.push(v);
}
}
}
}
}
floyd算法
时间复杂度
O(N3)
O
(
N
3
)
那就直接上一个最经典的5行式代码吧
储存请用邻接矩阵
void floyd(){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dist[i][k]+dist[k][j]<dist[i][j])
dist[i][j]=dist[i][k]+dist[k][j];
}
例题
例题1 hdu 2544 最短路裸题
题解
代码?上面不是一堆吗
例题2 POJ3259 Wormholes
给出一个图,让你判断是否存在负权环
题解
我们可以在SPFA的时候记录每个点入队次数
如果超过了N,则有负权环
为什么在SPFA算法中,判断负权回路的条件是任一节点进队次数超过总结点数? - 好地方bug的回答 - 知乎
https://www.zhihu.com/question/64299526/answer/221033086
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1000,M=100000;
struct node{
int u,v,next;
double w;
}edge[M];
int head[N],cnt;
int dist[N];
bool inqueue[N];
int intime[N];
queue<int> q;
void add_edge(int u,int v,double w){
++cnt;
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt;
}
bool SPFA(int s,int n){
memset(dist,0x3f,sizeof dist);
memset(inqueue,0,sizeof inqueue);
memset(intime,0,sizeof intime);
q.push(s);
dist[s]=0;
inqueue[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
inqueue[u]=0;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(dist[v]>dist[u]+edge[i].w){
dist[v]=dist[u]+edge[i].w;
if(!inqueue[v]){
inqueue[v]=1;
q.push(v);
intime[v]++;
if(intime[v]>n){
return 1;
}
}
}
}
}
return 0;
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n,m1,m2;
scanf("%d%d%d",&n,&m1,&m2);
memset(head,0,sizeof head);
cnt=0;
for(int i=1;i<=m1;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
for(int i=1;i<=m2;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,-w);
}
bool k=1;
for(int i=1;i<=n&&k;i++){
if(SPFA(i,n)){
printf("YES\n");
k=0;
}
}
if(k)
printf("NO\n");
}
}
例题3 POJ 3660 Cow Contest
N(N<=100)头奶牛,给出以下形如此的关系
A B 表示A的实力强于B
求有多少奶牛的名次能确定。数据保证不会自相矛盾
题解
连边,邻接矩阵这次我们开成bool数组,然后连边,跑floyd,如果某个点入度+出度=N-1,则该点可以确定排名,ans++
差分约束系统
概述
n个变量和m个不等式,形如
xi−xj<=ak
x
i
−
x
j
<=
a
k
求某一对
xt−xs
x
t
−
x
s
的最大值
那么也就是说,
xi<=xj+ak
x
i
<=
x
j
+
a
k
也就可以看成
disti<=distj+w(j,i)
d
i
s
t
i
<=
d
i
s
t
j
+
w
(
j
,
i
)
所以以s为起点,跑一遍最短路,
distt
d
i
s
t
t
即为所求
特殊情况:
1.存在负环:无解
2.无法到达t:
xt−xs
x
t
−
x
s
可以无限大
注意:此处的不等号必须带等号,否则需要转化
如果需要求的是最小值,可以将所有不等式转化为>=,然后跑最长路(此时出现正环为无解)
dfs版SPFA
判负环原理:如果在递归的松弛中,如果松弛了一个还在栈中的节点,则该点一定在负环上
void SPFA(int u){
vis[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
double w=edge[i].w;
if(dist[u]+w<dist[v]){
if(vis[v]){
fuquanhuan=true;//不能直接return,要清标记
break ;
}
else{
dist[v]=dist[u]+w;
SPFA(v);
}
}
}
vis[u]=0;
}
例题4 bzoj2330 糖果
输入的第一行是两个整数N,K。
接下来K行,表示这些点需要满足的关系,每行3个数字,X,A,B。
如果X=1, 表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多;
如果X=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果;
如果X=3, 表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果;
如果X=4, 表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果;
如果X=5, 表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果;
输出一行,表示lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出-1。
题解
标准的差分约束大版题
应该跑最长路
图例:u->v:w表示u到v连一条权值为w的边
建边:
1:a->b:0 b->a:0
2:a->b:1
3:b->a:0
4:b->a:1
5:a->b:0
额…为什么我首先想到小于是<然后看到不小于就以为是<=…
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define MAXN 100000
using namespace std;
typedef long long LL;
struct edge
{
int to,val;
edge(){};
edge(int _to,int _val):to(_to),val(_val){}
};
vector<edge> G[MAXN+5];
int dist[MAXN+5];
int times[MAXN+5],n;
bool inque[MAXN+5],flag=true;
queue<int> que;
void SPFA()
{
for(int i=1;i<=n;i++)
dist[i]=1,times[i]=1,inque[i]=true,que.push(i);
while(que.empty()==false)
{
int u=que.front();
que.pop();
inque[u]=false;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].to;
if(dist[v]<dist[u]+G[u][i].val)
{
dist[v]=dist[u]+G[u][i].val;
if(inque[v]==false){
que.push(v),inque[v]=true,times[v]++;
if(times[u]>n){
flag=false;
return;
}
}
}
}
}
}
int main()
{
int k,x,a,b;
scanf("%d %d",&n,&k);
for(int i=1;i<=k;i++)
{
scanf("%d %d %d",&x,&a,&b);
if(x==1)//a==b
{
G[a].push_back(edge(b,0));
G[b].push_back(edge(a,0));
}
else if(x==2)//a<b->b>=a+1
G[a].push_back(edge(b,1));
else if(x==3)//a>=b
G[b].push_back(edge(a,0));
else if(x==4)//a>b->a>=b+1
G[b].push_back(edge(a,1));
else if(x==5)//b>=a
G[a].push_back(edge(b,0));
}
SPFA();
if(flag==false)
{
printf("-1\n");
return 0;
}
LL tot=0;
for(int i=1;i<=n;i++)
tot+=1LL*dist[i];
printf("%lld\n",tot);
return 0;
}
例题5 bzoj 1486 最小圈
直接放链接吧,题目是图片
https://www.lydsy.com/JudgeOnline/problem.php?id=1486
题解
二分平均值x
01分数规划
那么我们把边的权值改为w-x,跑dfsSPFA判负环check即可
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=2e5+5;
struct node{
int u,v,nxt;
double w;
}e[M],edge[M];
int head[N],mcnt;
void add_edge(int u,int v,double w){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].w=w;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
bool vis[N];
double dist[N];
int n,m;
bool fuquanhuan;
void SPFA(int u){
vis[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
double w=edge[i].w;
if(dist[u]+w<dist[v]){
if(vis[v]){
fuquanhuan=true;
break ;
}
else{
dist[v]=dist[u]+w;
SPFA(v);
}
}
}
vis[u]=0;
}
void build_map(double mid){
memset(head,0,sizeof head);
mcnt=0;
for(int i=1;i<=m;i++){
add_edge(e[i].u,e[i].v,e[i].w-mid);
}
}
bool check(double mid){
build_map(mid);
memset(vis,0,sizeof vis);
memset(dist,0,sizeof dist);
fuquanhuan=false;
for(int i=1;i<=n;i++){
SPFA(i);
if(fuquanhuan)
return false;
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%lf",&e[i].u,&e[i].v,&e[i].w);
}
double l=-10000000,r=10000000;
int times=100;
while(times--){
double mid=(l+r)/2.0;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%.8lf\n",l);
}
习题1 bzoj 2118 墨墨的等式
墨墨突然对等式很感兴趣,他正在研究 a1x1+a2x2+…+anxn=B a 1 x 1 + a 2 x 2 + … + a n x n = B 存在非负整数解的条件,他要求你编写一个程序,给定N、{an}、以及B的取值范围,求出有多少B可以使等式存在非负整数解。
题解
算了我懒了
https://www.cnblogs.com/MashiroSky/p/5988262.html
习题2 bzoj 1922 大陆争霸
题目大意:
n个点m条边的带权有向图,你在1节点有无限个自爆机器人,有的点是被一些点所保护的,当所有保护这个点的点都已经被炸掉了之后,你才可以进入那个城市
现在问你最短炸掉n号节点的时间,保证有解
题解
定义d1表示到达i节点的最短时间
d2表示炸掉所有保护i的节点的最短时间
那么dist=max(d1,d2)
答案是dist[n]
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=3005,M=1e6;
typedef long long ll;
struct node{
ll u,v,w,nxt;
}edge[M];
ll head[N],mcnt;
void add_edge(ll u,ll v,ll w){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].w=w;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
struct mode{
ll u;
ll val;
mode(ll _u,ll _val){
u=_u;
val=_val;
}
};
bool operator <(mode a,mode b){
return a.val>b.val;
}
priority_queue<mode>q;
ll pro[N];
ll dist[N];
ll d1[N],d2[N];
vector<int>p[N];
bool vis[N];
void Dijkstra(){
memset(dist,-1,sizeof dist);
memset(d1,-1,sizeof d1);
memset(d2,-1,sizeof d2);
dist[1]=0;
d1[1]=0;
d2[1]=0;
q.push(mode(1,0));
while(!q.empty()){
ll u=q.top().u;
ll t=q.top().val;
q.pop();
//if(t>dist[u]&&dist[u]!=-1)
if(t>dist[u]||vis[u])
continue ;
vis[u]=1;
for(ll i=head[u];i;i=edge[i].nxt){
ll v=edge[i].v,w=edge[i].w;
if(d1[v]==-1||d1[v]>dist[u]+w){
d1[v]=dist[u]+w;
if(pro[v]==0){
dist[v]=max(d1[v],d2[v]);
q.push(mode(v,dist[v]));
}
}
}
for(ll i=0,sz=p[u].size();i<sz;i++){
ll v=p[u][i];
pro[v]--;
d2[v]=max(d2[v],dist[u]);
if(pro[v]==0&&d1[v]!=-1&&dist[v]==-1){
dist[v]=max(d1[v],d2[v]);
q.push(mode(v,dist[v]));
}
}
}
}
int main()
{
ll n,m;
scanf("%lld%lld",&n,&m);//ll
for(ll i=1;i<=m;i++){
ll u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
add_edge(u,v,w);
}
for(ll i=1;i<=n;i++){
scanf("%lld",&pro[i]);
for(ll j=1;j<=pro[i];j++){
ll x;
scanf("%d",&x);
p[x].push_back(i);
}
}
Dijkstra();
ll ans=dist[n];
if(ans==-1)
ans/=0;
printf("%lld\n",ans);
}
习题3 bzoj 2763 飞行路线
题目大意:n个点,m条带权无向边,你最多可以免费走k条边,问从s到t的最小花费 k<=10
题解
注意到k<=10
我们定义二维的dist数组然后跑最短路即可
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
const int N=10005,M=100005,K=15;
typedef long long ll;
typedef pair<int,int> pii;
struct node{
int u,v,w,nxt;
}edge[M];
int head[N],mcnt;
void add_edge(ll u,ll v,ll w){
mcnt++;
edge[mcnt].u=u;
edge[mcnt].v=v;
edge[mcnt].w=w;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
int dist[N][K];
bool inqueue[N][K];
queue<pii> q;
int s,t;
int n,m,k;
int ans=1<<30;
void SPFA(){
memset(dist,-1,sizeof dist);
dist[s][0]=0;
inqueue[s][0]=1;
q.push(pii(s,0));
while(!q.empty()){
int u=q.front().first,p=q.front().second;
q.pop();
if(u==t){
ans=min(ans,dist[u][p]);
}
inqueue[u][p]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v,w=edge[i].w;
if(dist[v][p]==-1||dist[v][p]>dist[u][p]+w){
dist[v][p]=dist[u][p]+w;
if(!inqueue[v][p]){
q.push(pii(v,p));
inqueue[v][p]=1;
}
}
if(p<k&&(dist[v][p+1]==-1||dist[v][p+1]>dist[u][p])){
dist[v][p+1]=dist[u][p];
if(!inqueue[v][p+1]){
q.push(pii(v,p+1));
inqueue[v][p+1]=1;
}
}
}
}
}
int main()
{
scanf("%d%d%d%d%d",&n,&m,&k,&s,&t);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
SPFA();
printf("%d\n",ans);
}
习题4 bzoj 4016 最短路径树问题
我还没做…QAQ