NOIP复习最短路总结

Posted Yiyi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NOIP复习最短路总结相关的知识,希望对你有一定的参考价值。

【模板】

 1 /*堆优化Dijkstra*/
 2 
 3 void dijkstra()
 4 {   
 5     priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > > que;//定义大顶堆
 6     for (int i=1;i<=n;i++) vis[i]=0,dis[i]=INF;
 7     dis[1]=0;
 8     que.push(make_pair<ll,int>(0,1));
 9     while (!que.empty())
10     {
11         int head=que.top().second;que.pop();
12         vis[head]=1;//从堆中弹出时表明找到了最短路,不再访问
13         for (int i=0;i<E[head].size();i++)
14         {
15             edge now=E[head][i];
16             if (!vis[now.to] && dis[now.to]>dis[head]+(ll)now.len)
17             {
18                 dis[now.to]=dis[head]+(ll)now.len;
19                 que.push(make_pair<ll,int>(dis[now.to],now.to));
20             }
21         }
22     }
23     printf("%lld",dis[n]);
24 }
 1 /*SPFA*/
 2 void spfa(int start)
 3 {
 4     memset(inque,0,sizeof(inque));
 5     for (int i=1;i<=tot;i++) dis[i]=INF;
 6     dis[start]=0;
 7     inque[start]=1;
 8     que.push(start);
 9     while (!que.empty())
10     {
11         int head=que.front();que.pop();
12         inque[head]=0;
13         for (int i=0;i<E[head].size();i++)
14         {
15             int to=E[head][i].to,len=E[head][i].len;
16             if (dis[to]>dis[head]+len)
17             {
18                 dis[to]=dis[head]+len;
19                 if (!inque[to])
20                 {
21                     inque[to]=1;
22                     que.push(to);
23                 }
24             }
25         }
26     }
27 }
 1 /*dfs版本SPFA-判断是否存在负环*/
 2 void spfa(int u)
 3 {
 4     if (vis[u])
 5     {
 6         flag=1;
 7         return;
 8     }
 9     vis[u]=1;
10     for (int i=0;i<E[u].size();i++)
11     {
12         int to=E[u][i].to,len=E[u][i].len;
13         if (dis[to]>dis[u]+len)
14         {
15             dis[to]=dis[u]+len;
16             spfa(to);
17             if (flag) return;
18         }
19     }
20     vis[u]=0;
21 }
22 
23 void solve()
24 {
25     memset(vis,0,sizeof(vis));
26     flag=0;
27     for (int i=1;i<=n;i++)
28     {
29         spfa(i);
30         if (flag) break; 
31     } 
32     puts(flag?"YES":"NO");
33 }
1 /*Floyd*/
2     for (int i=0;i<n;i++)
3         for (int j=0;j<n;j++)
4             for (int k=0;k<n;k++)
5                 f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

 

例题

【POJ1062】-有限制条件的最短路

题意:每个人都有一个物品,对应一定的钱数,想要得到此物品可以直接出钱,也可以通过用其他人的物品并添加一些钱来交换优惠购买物品。另外,每个人都有一个等级,要求和你交易的所有人中不能有任何两人的等级相差m以上。为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易"。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。 

思路:由于物品编号从1开始,我们假定0也是一个物品作为起点,它到其它物品的距离就是各个物品的原始价值。开始时,如果两种物品主人的等级限制M在规定范围以内,且j能用i替换,则将优惠价格视作从i到j的一条权值为优惠价的路径;如果在范围以外,就设为INF。处理主人地位等级限制条件的方法:我们依次枚举每一个物品,将它的等级L作为交易中等级最高的那一个,即可以参与交易的等级范围为[L-M,L],预处理时将这个范围以外的物品强制设置为已经访问过,再进行Dijkstra即可。

 

【POJ3255&BZOJ1726】-次短路

[方法一]在跑Dijkstra的过程中保留次短路。对于Dijkstra判断中取出的每一个点,如果到它的最短距离大于当前该点的次短距离,则当前该点已经取到最短距离和次短距离,不进行操作,否则进行两次判断:如果小于最短边,则赋给最短变,并将最短边赋给次短边;或者如果大于最短变且小于次短边,则赋给次短边。两次完成之后均要加入优先队列(!)。

 1 /*核心代码*/
 2         for 从当前点抵达的每一个点
 3         {
 4             int d=head.len+w[k];
 5             if (dis[v[k]]>d)
 6             {
 7                 swap(dis[v[k]],d);
 8                 temp.len=dis[v[k]];temp.num=v[k];
 9                 que.push(temp);
10             }
11             if (dis[v[k]]<d && secondis[v[k]]>d)
12             {
13                 secondis[v[k]]=d;
14                 temp.len=secondis[v[k]];temp.num=v[k];
15                 que.push(temp);
16             }
17         }

[方法二]正反跑两次SPFA。枚举每一条边,如果起点到一个端点的最短路+另一个端点到终点的最短路+该边长度 ≠ 最短路,则和答案比较更新最小值。

1 /*核心代码*/
2 /*dis1为起点到各个点的最短路,dis2为各个点到终点的最短路(又终点开始方向跑的最短路)*/
3    for (int i=1;i<=r;i++)
4     {
5         int now=dis1[u[i]]+dis2[v[i]]+w[i];
6         if (now!=mx) ans=min(ans,now);
7         now=dis1[v[i]]+dis2[u[i]]+w[i];
8         if (now!=mx) ans=min(ans,now);
9     }

 

【POJ1511】-从源点出发返回源点的最短路

思路:先跑一遍SPFA求出单源点最短路,再方向建图重新跑一次最短路。两者相加得到的和即为答案。

 

【POJ1860】-判断正环&带操作的边长

题意:有若干种货币,某些币种之间可兑换,给出各种兑换时的汇率和手续费,任何兑换都是双向的,但是两个方向的汇率和手续费可能不同,并告知你现在拥有的货币种类(只拥有一种)及数量,问是否可以通过货币建兑换最后回到本币种后钱数有所增加。

思路:SPFA跑最长路。注意题目中有向图的边长是需要通过运算得到的,按照一般最短路判断写法就可以了。

if (did[v]<(dis[u]-手续费)*汇率)

(不知道链接里一年前的自己在讲些什么)

 

BZOJ3436】-差分约束

题意:有n个值a1..an,得到一些诸如:ai与aj之差不大于x,ai与aj之差不小于y的约束条件,ai与aj相等的约束条件。问是否有可能?

思路:a>=b+c → b<=a-c,由a向b连一条-c的边;a<=b+c,由b向a连一条c的边;a=b → b<=a<=b,互相连一条0的边。dfs版SPFA判断负环。

 

POJ3169】-差分约束

n头牛从小到大排,它们之间某些距离不能大于一个值,某些距离不能小于一个值,求第一头牛和第N头牛之间距离的最大值。

思路:如BZOJ3436构造边,区别为本题限制条件不存在环状,最后求的是dis[n]-dis[i]<=S的S值,戟dis[n]<=dis[i]+S,用最短路径即可。

另外,如果题目要 求的是最大值,就将约束条件转化为">="形式,跑最长路。

 

【BZOJ4152】-建图!

题意:给定平面上的n个点,定义(x1,y1)到(x2,y2)的费用为min(|x1-x2|,|y1-y2|),求从1号点走到n号点的最小费用。

思路:按照某维坐标排序,相邻两个点在这一维度上的差值最小,所以两两连边,长度为这一维度上的差值然后跑最短路即可。

 

OpenJudge3368】-限制访问顺序,多条路径覆盖全图的最小值(!)

题意:诸葛亮要征服N城市。然而,City-X在击败City-2,City-3……City-x-1后击败。关羽,张飞,赵云,每个人都应该领导一个军队。三个军队从City-0出发,征服所有的城市并返回City-0。求三个军队的行程总长的最小值。

 思路:首先求出最短路径。我们用dp[i][j][k]表示当前三个部队分别位于i、j、k时的答案(j<=k<=i)。分别考虑从i、j、k中的一支部队走到i+1的情况,总共三个递推式。对于返回,直接假设他们都走到最终状态后统一返回,即0到各自最终状态的最短路径。由于dp[i+1]只和dp[i]有关,可以使用滚动数组。

 1 /*核心代码*/
 2 for(int i=0;i<n;++i)
 3     {
 4         cur^=1;
 5         memset(dp[cur],0x3f,sizeof(dp[cur]));//注意每次滚动数组都要清成无穷大,i=3的时候dp[0][0][0]!=0
 6         for(int j=0;j<=i;++j)
 7             for(int k=j;k<=i;++k)
 8             {
 9                 dp[cur][j][k]=min(dp[cur][j][k],dp[1-cur][j][k]+dist[i+1][i]);
10                 dp[cur][k][i]=min(dp[cur][k][i],dp[1-cur][j][k]+dist[j][i+1]);
11                 dp[cur][j][i]=min(dp[cur][j][i],dp[1-cur][j][k]+dist[k][i+1]);
12                 if (i==n-1) 
13                 {
14                     ans=min(ans,(ll)dp[cur][j][k]+(ll)dist[0][n]+(ll)dist[0][j]+(ll)dist[0][k]);
15                     ans=min(ans,(ll)dp[cur][k][i]+(ll)dist[0][n]+(ll)dist[0][k]+(ll)dist[0][i]);
16                     ans=min(ans,(ll)dp[cur][j][i]+(ll)dist[0][n]+(ll)dist[0][j]+(ll)dist[0][i]);
17                 }
18             }
19     }

 

BZOJ4093】含有中枢的最短路(!!)

题意:有连接N (1 < = N < = 20,000)个农场的航班。对于任何航班,指定了其中的k个农场作为枢纽。 (1 < = K <= 200 , K < = N)。目前,共有M种单向航班( 1 < = M < = 20,000 ),第i个航班从农场u_i至农场v_i花费d_i ( 1 < = d_i < =10,000 )美元。航班保证u_i或者v_i至少有一个是枢纽,任意两个农场至多只有一个航班,保证u_i≠v_i。共收到Q个度假请求,(1 < = Q < = 50,000),其中第i个请求是从农场a_i至农场b_i,问每个请求是否满足 ,并计算能满足的度假请求的最小费用总和。

 思路:显然,每个非中枢的周围必定是中枢,也就是说,中枢间可以通过直接相连,或通过一个非中枢点连接。预处理 直接相连 或 间隔一个非中枢点 相连的两个中枢的距离,用Floyd求出所有中枢之间的最短路。接下来处理出所有的中枢到所有节点之间的最短路。由于已知两个中枢(u,v)之间的最短路,对于从v出发抵达的下一个农场vv,很容易得到(u,vv)。最后对于查询的(a,b),如果a是一个中枢,直接可以利用上一步处理出来的信息。否则枚举a指向的每一个中枢c和b之间的最短路,再加上ac的距离,最小的那个即为最短路。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXK=200+5;
const int MAXN=20000+50;
const ll INF=1e12;
struct node
{
    int to,dis;
};
vector<node> E[MAXN];
int n,m,k,q;
ll d[MAXK][MAXK],dis[MAXK][MAXN];
int id[MAXN],num[MAXN];

void addedge(int u,int v,int w)
{
    E[u].push_back((node){v,w}); 
}

void init()
{
    scanf("%d%d%d%d",&n,&m,&k,&q);
    memset(id,0,sizeof(id));

    for (int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
    }
    for (int i=1;i<=k;i++)
    {
        int x;
        scanf("%d",&x);
        id[x]=i,num[i]=x;
    }
}

void prep()
{        
    for (int i=1;i<=k;i++)
        for (int j=1;j<=k;j++)
            d[i][j]=INF;
    for (int i=1;i<=k;i++) d[i][i]=0;
    for (int i=1;i<=k;i++)
    {
        int u=num[i];
        for (int j=E[u].size()-1;j>=0;j--)
        {
            int v=E[u][j].to;
            if (id[v]) d[i][id[v]]=min(d[i][id[v]],(ll)E[u][j].dis);
                else
                {
                    for (int _k=E[v].size()-1;_k>=0;_k--)
                    {
                        int vto=E[v][_k].to;
                        if (id[vto] && vto!=num[i]) d[id[u]][id[vto]]=min(d[id[u]][id[vto]],(ll)E[u][j].dis+(ll)E[v][_k].dis);
                    }
                }
        }
    }    
    
    for (int _k=1;_k<=k;_k++)
        for (int i=1;i<=k;i++)
            for (int j=1;j<=k;j++)
                if (i!=j && j!=_k && _k!=i) d[i][j]=min(d[i][j],d[i][_k]+d[_k][j]); 
}

void prep2()
{
    for (int i=1;i<=k;i++)
        for (int j=1;j<=n;j++) dis[i][j]=INF;
    for (int i=1;i<=k;i++)
        for (int j=1;j<=k;j++) dis[i][num[j]]=d[i][j];
        
        
        
    for (int i=1;i<=k;i++)
    {
        for (int _k=0;_k<E[num[i]].size();_k++)//注意这里是E[num[i]]不是num[i],检查了40分钟才发现QAQ
            for (int j=1;j<=k;j++)
            {
                int to=E[num[i]][_k].to;
                dis[j][to]=min(dis[j][to],d[j][i]+E[num[i]][_k].dis);
            }
    }
}

void solve()
{
    int t=0;
    ll totalans=0;
    for (int i=0;i<q;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        ll ans=INF;
        if (id[a]) ans=dis[id[a]][b];
            else
            {
                for (int j=0;j<E[a].size();j++)
                {
                    int v=E[a][j].to;
                    if (id[v]) ans=min(ans,E[a][j].dis+dis[id[v]][b]);
                }
            }
        if (ans<INF)
        {
            totalans+=ans;
            t++;
        }
    }
    printf("%d\\n",t);
    printf("%d",totalans);
}

int main()
{
    init();
    prep();
    prep2();
    solve();
    return 0;
}
完整代码

 

BZOJ1706】-恰巧经过n条边的最短路。

思路:Floyd矩阵乘法快速幂

 1 /*核心代码*/
 2 node operator * (node a,node b)
 3 {
 4     node c;
 5     for (int i=1;i<=cnt;i++)
 6         for (int j=1;j<=cnt;j++) c.dis[i][j]=INF;
 7     for (int k=1;k<=cnt;k++)
 8         for (int i=1;i<=cnt;i++)
 9             for (int j=1;j<=cnt;j++)
10                 c.dis[i][j]=min(c.dis[i][j],a.dis[i][k]+b.dis[k][j]);
11     return c;
12 }
13 
14 void solve()
15 {
16     int k=n-1;
17     result=f,origin=f;//f为初始的邻接矩阵
18     while (k)
19     {
20         if (k&1) result=result*origin;
21         k>>=1;
22         origin=origin*origin;
23     }
24     printf("%lld",result.dis[id[s]][id[e]]); 
25 }

 


 

估计NOIP不会考的平面图转最小割

【BZOJ1001狼抓兔子】

【BZOJ2007海拔】

以上是关于NOIP复习最短路总结的主要内容,如果未能解决你的问题,请参考以下文章

NOIp 2016 总结

最短路总结

noip复赛会考哪些类型的题目?该如何复习?

2017-2018OI学习记录

[题解]「最短路,Noip2009」最优贸易

NOIP算法总结与复习