复习:
SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。
改为deque<int>
LLL:Large Label Last 策略(不常用),设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。
一本通题目(不全是优化)
1504: 【例 1】Word Rings
将一个字符串看成一条边,字符两端的字符看成节点,长度看成权值。二分枚举答案,最后SPFA刷正环,因为只要有一个正环存在就可以了。
把每个单词视为一条边,由开头两字母指向结尾两字母,边权为单词长度。则原问题转化为求一个最优比率环。(和最优比率生成树很像哈)
可以利用二分答案+spfa判环来解决。点最多有26*26个,边最多1e5.dfs版spfa判环就是快。。
若不存在环串,输出 No solution,否则输出最长的环串的平均长度。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; const double eps=1e-4; typedef long long LL; //将一个字符串看成一条边,字符两端的字符看成节点,长度看成权值。二分枚举答案,最后SPFA刷正环,因为只要有一个正环存在就可以了。 //把每个单词视为一条边,由开头两字母指向结尾两字母,边权为单词长度。则原问题转化为求一个最优比率环, //可以利用二分答案+spfa判环来解决。点最多有26*26个,边最多1e5.dfs版spfa判环就是快。。 //这道题还是不太理解 //我知道了,我这个题都不读的女人 //若不存在环串,输出 No solution,否则输出最长的环串的平均长度。 char aa[1010]; int head[maxn],vis[maxn]; double dis[maxn]; int n,cnt; struct node{ int to,next,dis; }ed[maxn*10]; void add(int a,int b,int c){ ed[++cnt].to=b; ed[cnt].dis=c; ed[cnt].next=head[a]; head[a]=cnt; } int js(char a,char b){ return (a-'a')*26+(b-'a')+1; //要加1 } bool spfa(int x,double mid){ vis[x]=1; for(int i=head[x];i;i=ed[i].next){ int t=ed[i].to; if(dis[x]+ed[i].dis-mid>dis[t]){ //!!!!因为是想最大嘛 dis[t]=dis[x]+ed[i].dis-mid; if(vis[t]||spfa(t,mid)) { //如果t已经访问过或者后面能够返回1,那么就存在环,就可以返回1 vis[x]=0; return 1; } } } vis[x]=0; return 0; } bool judge(double mid){ memset(dis,0,sizeof(dis)); for(int i=1;i<=26*26;i++){ //枚举!!!!!!点最多有26*26个,枚举以每一个点为起点 if(spfa(i,mid)) return 1; } return 0; } int main(){ while(scanf("%d",&n)&&n){ memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); for(int i=0;i<n;i++){ scanf("%s",aa); int len=strlen(aa); add(js(aa[0],aa[1]),js(aa[len-2],aa[len-1]),len); //建边 } double mid,left=0,right=1000; //二分 while(right-left>=eps){ mid=(left+right)/2; if(judge(mid)){ left=mid; } else right=mid; } if(left==0) printf("No solution "); else printf("%.2lf ",left); } return 0; }
1505:【例 2】双调路径
【题目描述】
原题来自:BalticOI 2002
如今的道路收费发展很快。道路的密度越来越大,因此选择最佳路径是很现实的问题。城市的道路是双向的,每条道路有固定的旅行时间以及需要支付的费用。
路径是连续经过的道路组成的。总时间是各条道路旅行时间的和,总费用是各条道路所支付费用的总和。一条路径越快,或者费用越低,该路径就越好。严格地说,如果一条路径比别的路径更快,而且不需要支付更多费用,它就比较好。反过来也如此理解。如果没有一条路径比某路径更好,则该路径被称为最小路径。
这样的最小的路径有可能不止一条,或者根本不存在路径。
问题:读入网络,计算最小路径的总数。费用时间都相同的两条最小路径只算作一条。你只要输出不同种类的最小路径数即可。
【输入】
第一行有四个整数,城市总数 n,道路总数 m,起点和终点城市 s,e;
接下来的 m 行每行描述了一条道路的信息,包括四个整数,两个端点 p,r,费用 c,以及时间 t;
两个城市之间可能有多条路径连接。
【输出】
仅一个数,表示最小路径的总数。
很明显,这是一道最短路的题目
但是有两个状态
所以我们不能直接开数组
要把一个状态存在树祖中,另一个为下标
因为我们知道一条路径为最优的情况只有当它的一个指标大于另一个最优路径且它的另一个指标小于那条路径(否则另外一条路径就不是最优解了)
由此可得,最优解的集合中两个指标分别呈上升和下降趋势
//其中每条道路有两个关键值,可以联想到一维背包,dp[i][j]表示第i个城市花费j钱时的最小时间;
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=110; const int INF=0x3f3f3f3f; typedef long long LL; /* 很明显,这是一道最短路的题目 但是有两个状态 所以我们不能直接开数组 要把一个状态存在树祖中,另一个为下标 因为我们知道一条路径为最优的情况只有当它的一个指标大于另一个最优路径且它的另一个指标小于那条路径(否则另外一条路径就不是最优解了) 由此可得,最优解的集合中两个指标分别呈上升和下降趋势 */ //其中每条道路有两个关键值,可以联想到一维背包,dp[i][j]表示第i个城市花费j钱时的最小时间; int head[maxn]; int vis[maxn][maxn*100]; LL f[maxn][maxn*100];//f[i][j]代表到第i个点的费用为j时的最小时间 int cnt,n,m,s,e; struct node{ int to,next,ti,wei; }ed[610]; struct tt{ int pos,money; }; void add(int x,int y,int z,int d){ cnt++; ed[cnt].to=y; ed[cnt].next=head[x]; ed[cnt].ti=z; ed[cnt].wei=d; head[x]=cnt; } void spfa(int st){ tt temp; temp.pos=st; temp.money=0; queue<tt> q; q.push(temp); vis[st][0]=1; f[st][0]=0; while(!q.empty()){ tt op=q.front(); q.pop(); vis[op.pos][op.money]=0; if(op.money>=(n-1)*100) continue; //这句!!!!因为题目说了时间、金钱都小于10 for(int i=head[op.pos];i;i=ed[i].next){ int v=ed[i].to; if(f[v][op.money+ed[i].wei]>f[op.pos][op.money]+ed[i].ti){ //如果自己的时间要比较长一点 f[v][op.money+ed[i].wei]=f[op.pos][op.money]+ed[i].ti; if(!vis[v][op.money+ed[i].wei]){ vis[v][op.money+ed[i].wei]=1; tt ne; ne.money=op.money+ed[i].wei;ne.pos=v; q.push(ne); } } } } } int main(){ scanf("%d %d %d %d",&n,&m,&s,&e); int a,b,c,d; for(int i=0;i<m;i++){ scanf("%d %d %d %d",&a,&b,&c,&d); add(a,b,c,d); add(b,a,c,d); } memset(f,0x3f,sizeof(f)); spfa(s); int mintime=INF,ans=0; for(int i=0;i<=n*100;i++){ if(f[e][i]==INF||f[e][i]>=mintime) continue; mintime=f[e][i]; ans++; } printf("%d ",ans); return 0; }
1506:最小圈
很明显是个分数规划的题目 如果我们能够在图中找到一个负环,就说明ans可以继续缩小 使用实数二分即可
跟word rings比较像,但是这个是求最小
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3010; const int maxm=1e4+10; const int INF=0x3fffffff; const int eps=1e-9; typedef long long LL; //很明显是个分数规划的题目 如果我们能够在图中找到一个负环,就说明ans可以继续缩小 使用实数二分即可 int n,m; int head[maxn]; struct node{ int to,next; double dis; }ed[maxm]; int vis[maxn],cnt; double dis[maxn]; void adde(int a,int b,double c){ cnt++; ed[cnt].to=b; ed[cnt].next=head[a]; ed[cnt].dis=c; head[a]=cnt; } bool dfs(int now,double mid){ vis[now]=1; for(int i=head[now];i!=0;i=ed[i].next){ int t=ed[i].to; if(dis[t]>dis[now]+ed[i].dis-mid){ dis[t]=dis[now]+ed[i].dis-mid; if(vis[t]) return 1; else if(dfs(t,mid)) return 1; } } vis[now]=0; return 0; } bool ju(double mid){ memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); for(int i=1;i<=n;i++){ if(dfs(i,mid)) return 1; } return 0; } int main(){ scanf("%d %d",&n,&m); int x,y; double z; double l=1e8,r=-1e8,mid; for(int i=0;i<m;i++){ scanf("%d %d %lf",&x,&y,&z); adde(x,y,z); //有向图 l=min(l,z); r=max(r,z); } while(r-l>eps){ mid=(l+r)/2.0; if(ju(mid)) r=mid; else l=mid; } printf("%.8lf ",l); return 0; }
1507:虫洞 Wormholes
看穿这道题的本质:求负环,把虫洞的边弄为负值,然后一个点入队次数超过n就存在
所以spfa就好
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; //看穿这道题的本质:求负环,把虫洞的边弄为负值,然后一个点入队次数超过n就存在 //所以spfa就好 struct node{ int to,next,t; }ed[10005]; int head[2501]; int dis[510],num[510],vis[510]; int f,n,m,w,cnt; void adde(int a,int b,int c){ ed[++cnt].to=b; ed[cnt].next=head[a]; ed[cnt].t=c; head[a]=cnt; } bool spfa(){ queue<int> q; for(int i=1;i<=n;i++) dis[i]=INF; dis[1]=1; vis[1]=1; num[1]++; q.push(1); while(!q.empty()){ int op=q.front(); q.pop(); vis[op]=0; for(int i=head[op];i;i=ed[i].next){ int tt=ed[i].to; if(dis[tt]>dis[op]+ed[i].t){ dis[tt]=dis[op]+ed[i].t; if(!vis[tt]){ vis[tt]=1; num[tt]++; q.push(tt); if(num[tt]==n) return 1; } } } } return 0; } int main(){ scanf("%d",&f); while(f--){ scanf("%d %d %d",&n,&m,&w); memset(vis,0,sizeof(vis)); memset(num,0,sizeof(num)); memset(head,0,sizeof(head)); cnt=0; //这个记得清零 for(int i=1;i<=m;i++){ int x,y,z; scanf("%d %d %d",&x,&y,&z); adde(x,y,z); adde(y,x,z); } for(int i=1;i<=w;i++){ int x,y,z; scanf("%d %d %d",&x,&y,&z); adde(x,y,-z); //边权为负的 } if(spfa()) printf("YES "); else printf("NO "); } return 0; }
1508:Easy SSSP
输入数据给出一个有 N 个节点,M 条边的带权有向图。要求你写一个程序,判断这个有向图中是否存在负权回路。如果从一个点沿着某条路径出发,又回到了自己,而且所经过的边上的权和小于 0,就说这条路是一个负权回路。
如果存在负权回路,只输出一行 −1;如果不存在负权回路,再求出一个点S到每个点的最短路的长度。约定:S 到 S 的距离为 0,如果 S 与这个点不连通,则输出 NoPath。
可喜可贺!!!终于用到了优化啦~
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int maxm=1e5+10; const int INF=0x3fffffff; typedef long long LL; LL n,m,s,cnt; LL head[maxn]; //负权环可能没有和起点连在一起!!! //可以开一个布尔数组,如果这个点入过队,就把它标记为true.跑了一遍SPFA之后返回枚举没有入过队的点再跑一遍就行啦. struct node{ LL to,dis,next; }ed[maxm]; LL dis[maxn]; int vis[maxn]; //保存有没有入过队 LL ans[maxn]; //最后的结果,保存起点的, LL num[maxn]; int isok[maxn]; //保存有没有访问过 void add(LL a,LL b,LL c){ ed[++cnt].to=b; ed[cnt].next=head[a]; ed[cnt].dis=c; head[a]=cnt; } deque<LL> q; int spfa(LL st){ for(int i=1;i<=n;i++) dis[i]=INF; for(int i=1;i<=cnt;i++) num[i]=0; //每次都要清一次0 dis[st]=0; if(!q.empty()) q.pop_front(); //清空 //queue<int> q; q.push_back(st); while(!q.empty()){ LL op=q.front(); q.pop_front(); vis[op]=0; for(int i=head[op];i;i=ed[i].next){ LL tt=ed[i].to; if(dis[tt]>dis[op]+ed[i].dis){ dis[tt]=dis[op]+ed[i].dis; if(!vis[tt]){ isok[tt]=1; num[tt]++; if(num[tt]>n) return -1; if(q.empty()) q.push_back(tt); //没办法,只有一种放法 else{ if(dis[tt]<dis[q.front()]) q.push_front(tt); //放前面 else q.push_back(tt); //放后面 } vis[tt]=1; } } } } return 1; } int main(){ scanf("%lld %lld %lld",&n,&m,&s); LL x,y,z; for(int i=1;i<=m;i++){ scanf("%lld %lld %lld",&x,&y,&z); add(x,y,z); } int ok=spfa(s); if(ok==-1){ printf("-1"); return 0; } for(int i=1;i<=n;i++) ans[i]=dis[i]; //!!!!在这里保存答案 for(int i=1;i<=n;i++){ if(!isok[i]){ ok=spfa(i); if(ok==-1){ printf("-1"); return 0; } } } for(int i=1;i<=n;i++){ if(dis[i]==INF) printf("NoPath "); else printf("%lld ",ans[i]); } return 0; } //错了两个点 #include<bits/stdc++.h> #define inf 0x6fffffff #define ll long long #define maxn 1005 using namespace std; struct node{ ll v,w,nxt; }edge[maxn*100]; ll n,m,cnt[maxn],dis[maxn],k=1,s,head[maxn],ans[maxn]; bool vis[maxn],done[maxn]; inline void adde(ll u,ll v,ll w) { edge[k].v=v; edge[k].w=w; edge[k].nxt=head[u]; head[u]=k++; } deque<ll> q; inline bool SPFA(ll s) { for(int i=1;i<=n;i++) cnt[i]=0; if(!q.empty()) q.pop_front(); for(int i=1;i<maxn;i++) dis[i]=inf; dis[s]=0; q.push_back(s); while(!q.empty()) { ll u=q.front();q.pop_front(); vis[u]=0; for(int i=head[u];i;i=edge[i].nxt) { ll v=edge[i].v; if(dis[v]>dis[u]+edge[i].w) { dis[v]=dis[u]+edge[i].w; if(!vis[v]) { done[v]=1; cnt[v]++; if(cnt[v]>n) return 0; if(q.empty()) q.push_back(v); else { if(dis[v]<dis[q.front()]) q.push_front(v); else q.push_back(v); } vis[v]=1; } } } } return 1; } int main() { scanf("%lld%lld%lld",&n,&m,&s); for(int i=1;i<=m;i++) { ll t1,t2,t3; scanf("%lld%lld%lld",&t1,&t2,&t3); adde(t1,t2,t3); } int haha=SPFA(s); if(!haha) { printf("-1"); return 0; } for(int i=1;i<=n;i++) ans[i]=dis[i]; for(int i=1;i<=n;i++) { if(!done[i]) { haha=SPFA(i); if(!haha) { printf("-1"); return 0; } } } for(int i=1;i<=n;i++) { if(ans[i]<inf) printf("%lld ",ans[i]); else printf("NoPath "); } return 0; }