这一段时间复习了一下最短路,做了几道非常典型特别考察最短路性质的题
1.P1144 最短路计数
这个题主要考察对松弛操作的理解。
关键代码
if(dis[v] > dis[u] + 1) { dis[v] = dis[u] + 1; ans[v] = ans[u]; q.push(make_pair(dis[v],v)); } else if(dis[v] == dis[u] + 1) { ans[v] += ans[u]; ans[v] %= mo; }
2.CF786B Legacy
线段树优化建边,对于一个点,与一个连续区间里的点连边,可以利用线段树的特性来降低连边复杂度。
void build1(int &p,int l,int r) { if(l==r) { p=l;return; } p=++tot; int mid=l+r>>1; build1(lc[p],l,mid); build1(rc[p],mid+1,r); add(p,lc[p],0);add(p,rc[p],0); }
3.P1772 [ZJOI2006]物流运输
这是一道DP加最短路的题目,由于数据很小,我们的复杂度可以很高
设计这样的一个DP。f[i] 表示前i天的花费,考虑转移,考虑第j天是否改变航线
方程为:f[i] = min(f[i],f[j-1] + (i-j+1) * x + k);
对于每一次变化,我们都要求一次最短路。
f[0] = -k; for(int i = 1;i <= n;i ++) { for(int j = 1;j <= m ;j++)flag[j] = 0; for(int j = i;j >= 1;j--) { for(int a = 1;a <= m ;a++) if(pd[a][j])flag[a] = 1; int x = spfa(1); if(x >= dis[0])break; f[i] = min(f[i],f[j-1] + (i-j+1)*x + k); } }
4.P2868 [USACO07DEC]观光奶牛Sightseeing Cows
01分数规划,懒得说了大家自己百度吧,主要是二分加图论结合。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> #include<set> #include<vector> #include<cmath> #include<map> using namespace std; typedef long long ll; inline int read() { char ch=getchar();int x=0,f=1; while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch<='9' && ch>='0') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } inline ll readl() { char ch=getchar();ll x=0,f=1; while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch<='9' && ch>='0') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } const int maxn = 5001; int n,m; int a[maxn],vis[maxn],num[maxn]; int l,pre[maxn<<1],last[maxn],other[maxn<<1]; double len[maxn<<1],dis[maxn]; queue<int> q; void add(int x,int y,int z) { l++; pre[l] = last[x]; last[x] = l; other[l] = y; len[l] = z; } bool spfa(int x,double y) { memset(num,0,sizeof num); memset(vis,0,sizeof vis); vis[x] = 1; memset(dis,0,sizeof dis); dis[x] = 0; q.push(x); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; if(num[u] >= n)return 1; for(int p = last[u];p;p = pre[p]) { int v = other[p]; if(dis[v] > dis[u] + y*len[p] - a[u]) { dis[v] = dis[u] + y*len[p] - a[u]; if(vis[v] == 0) { vis[v] = 1; num[v] ++; if(num[v] >= n)return 1; q.push(v); } } } } return 0; } bool check(double x) { //cout<<x<<endl; for(int i = 1;i <= n ;i++) if(spfa(i,x))return 1; return 0; } int main(){ n = read(),m = read(); for(int i = 1;i <= n ;i++)a[i] = read(); for(int i = 1;i <= m;i++) { int a = read(),b = read(),c = read(); add(a,b,c); } double l = 0,r = 1000000,ans = 0; while(r - l >= 0.0001) { double mid = (l + r) / 2; if(check(mid)) l = mid + 0.0001,ans = mid; else r = mid - 0.0001; } printf("%.2lf",ans); return 0; }
5.P2939 [USACO09FEB]改造路Revamping Trail
分层图最短路板子题,观察题面,发现k很小,我们就可以建k张图,图与图之间建零花销边,最后跑一边最短路就OK了
for(int i = 1;i <= m ;i++) { int a = read(),b = read(),c = read(); add(a,b,c);add(b,a,c); for(int j = 1;j <= k ;j ++) { add(a + j*n,b + j*n,c); add(b + j*n,a + j*n,c); add(a + (j-1)*n,b + j*n,0); add(b + (j-1)*n,a + j*n,0); } }
6.P1613 跑路
倍增思想与Floyd的结合\(f[i][j][p] = (f[i][k][p-1] , f[k][j][p-1])\)
\(f[i][j][p]\) 表示i到j距离是否为\(2^{p}\)。
for(int p = 1;p <= 64;p ++){ for(int k = 1;k <= n ;k ++){ for(int i = 1;i <= n ;i ++){ for(int j = 1;j <= n ;j ++){ if(f[i][k][p-1] && f[k][j][p-1]){ f[i][j][p] = 1; } } } } }
7.P3393 逃离僵尸岛
最短路常见套路,加一个虚拟节点来简化问题,将所有危险城市用虚拟节点连起来统一处理距离小于等于s的,由于多建了一些边,所以要开大一些空间。
for(int i = 1;i <= k ;i ++){ int c = read();flag[c] = 1; add(n+1,c);add(c,n+1); } for(int i = 1;i <= n ;i ++)w[i] = p; dij(n+1); for(int i = 1;i <= n ;i ++){ if(dis[i] <= s+1){ w[i] = q; } }
8.P1606 [USACO07FEB]白银莲花池Lilypad Pond
简单分析,发现这就是一个最短路模型,对于第一问怎么建图都ok,但对于第二问如果将水和荷叶相连会导致计数重复,所以将水和可以通过荷叶到达的水相连,再跑最短路计数
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> #include<set> #include<vector> #include<cmath> using namespace std; typedef long long ll; const int maxn = 1e6 + 7; int n,m,st,ed; int l,pre[maxn],last[maxn],other[maxn],len[maxn]; int mp[1010][1010],id[1010][1010]; int flag[1010][1010]; ll cnt[maxn]; int vis[maxn],que[maxn],dis[maxn]; int dx[9]={0,-2,-1,1,2,2,1,-1,-2}; int dy[9]={0,1,2,2,1,-1,-2,-2,-1}; void add(int x,int y,int z){ l ++; pre[l] = last[x]; last[x] = l; other[l] = y; len[l] = z; } void dfs(int p,int x,int y){ if(flag[x][y])return ; flag[x][y] = 1; for(int i = 1;i <= 8;i ++){ int xx = x + dx[i]; int yy = y + dy[i]; if(xx <= 0 || yy <= 0 ||xx > n || yy > m || flag[xx][yy])continue; if(mp[xx][yy] == 1)dfs(p,xx,yy); flag[xx][yy] = 1; add(p,id[xx][yy],1); } } void spfa(int x){ memset(dis,0x3f,sizeof dis); dis[x] = 0; que[1] = x; vis[x] = 1; cnt[x] = 1; int h = 0,t = 1; while(h != t){ h ++; int u = que[h]; vis[u] = 0; for(int p = last[u];p;p = pre[p]){ int v = other[p]; if(dis[v] == dis[u] + len[p]){ cnt[v] += cnt[u]; } if(dis[v] > dis[u] + len[p]){ cnt[v] = cnt[u]; dis[v] = dis[u] + len[p]; if(!vis[v]){ vis[v] = 1; que[++t] = v; } } } } } int main() { scanf("%d%d",&n,&m); for(int i = 1;i <= n ;i ++){ for(int j = 1;j <= m;j ++){ scanf("%d",&mp[i][j]); id[i][j] = (i-1)*m + j; if(mp[i][j] == 3)st = id[i][j]; if(mp[i][j] == 4)ed = id[i][j]; } } for(int i = 1;i <= n;i ++){ for(int j = 1;j <= m;j ++){ if(mp[i][j] == 2 || mp[i][j] == 4)continue; memset(flag,0,sizeof flag); dfs(id[i][j],i,j); } } spfa(st); if(dis[ed] == dis[0]){ cout<<-1<<endl;return 0; } else { cout<<dis[ed] - 1<<endl; cout<<cnt[ed]<<endl; } return 0; }
9.T51485 键盘
题解:分析题目,很像一个DP,但是存在删除键,所以存在后效性,但是可以对每个操作连边跑最短路。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> #include<set> #include<vector> #include<cmath> using namespace std; typedef long long ll; const int maxn = 1e6 + 7; int dis[maxn],vis[maxn]; int n,x; void spfa(){ memset(dis,63,sizeof dis); queue <int> q; if(x >= n){ dis[0] = 3;dis[n] = x - n; q.push(0); vis[0] = 1; } else { dis[x] = 0; q.push(x); vis[x] = 1; } while(!q.empty()){ int u = q.front(); q.pop(); vis[u] = 0; if(u < n &&dis[u + 1] > dis[u] + 1){ dis[u + 1] = dis[u] + 1; if(!vis[u + 1]){ vis[u + 1] = 1; q.push(u + 1); } } if(u > 1&&dis[u - 1] > dis[u] + 1){ dis[u - 1] = dis[u] + 1; if(!vis[u - 1]){ vis[u - 1] = 1; q.push(u - 1); } } if(u > 0){ for(int i = 2;u*(i-1) <= n;i ++){ if(u * i >= n&&dis[n] > dis[u] + 2*(i + 1) + u*i - n){ dis[n] = dis[u] + 2*(i + 1) + u*i - n; } if(u * i < n && dis[u*i] > dis[u] + 2*(i + 1)){ dis[u*i] = dis[u] + 2*(i + 1); if(vis[u*i] == 0){ vis[u*i] = 1; q.push(u*i); } } } } } } int main() { scanf("%d%d",&x,&n); spfa(); printf("%d",dis[n]); return 0; }
来源:https://www.cnblogs.com/wtz2333/p/12233121.html