最短路总结

这一段时间复习了一下最短路,做了几道非常典型特别考察最短路性质的题

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;
}



posted @ 2020-01-25 16:00  wtz2333  阅读(130)  评论(0)    收藏  举报