(DP006)洛谷P1772 [ZJOI2006]物流运输

一、算法分析

先分析题意,首先看到题目,物流运输,再结合题意,发现这个问题并不是问从起点走到终点只走一次的情况。而是问在每天都有物流的情况下,如何规划路线,使得各天的物流成本的总和最小。这道题还设置了某些天的某些点是不可达的,因此需要及时地更换新路线,而更换新路线是需要成本的。自己刚开始看这道题的时候没有头绪,然后看了标签之后发现是DP结合最短路来做,即外层是一个简单的线性DP,内层是最短路。对于DP部分,设
f[i]表示前i天的最小花费,则可以选择第j到第i天走同一条路,j是决策变量。状态转移方程详见代码及注释。

二、代码及注释

//注意先分析清楚样例,这是一个运输公司,要的是制定规划,而不是只运一次货物
//最短路结合DP的问题
//spfa在执行的时候可以选择不经过一些点
//从DP的可行性上来分析,倒序枚举有利于剪枝
#include
#include
#include
#include
#include
#define l first
#define r second
using namespace std;
const int N=105;                          //天数
const int M=25*25*2;                      //边数
const int inf=0x3f3f3f3f;
typedef pair<int,int> PII;
vector<PII> limit[25];                    //某个码头的不可用的时间,用vector来存
int h[25],w[M],e[M],ne[M],idx;            //链式前向星存图
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int n,m,cost,edges;                       //运货天数,点数,修改成本,边数
int f[N];           
int q[25],dist[25];
int st[25];
bool check(int begin,int end,int l,int r){//写个函数来判断两个区间有没有公共部分
    if(begin>l){
        swap(begin,l);
        swap(end,r);
    }
    if(l<=end) return true;
    return false;
}

int spfa(int begin,int end){                  //begin到end时间里面都可用的点才能用来更新
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    int hh=0,tt=1;
    q[0]=1;
    st[1]=1;
    while(hh!=tt){
        int t=q[hh++];
        if(hh==N) hh=0;
        st[t]=0;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];                   //注意这里要判断是否可行
            int ok=1;
            for(int k=0;k<limit[j].size();k++) 
                if(check(begin,end,limit[j][k].l,limit[j][k].r)){
                    ok=0;
                    break;
                }
            if(!ok) continue;             //如果有冲突就跳过
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]){
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=1;
                }
            }
        }
    }
    return dist[m];
}

int main(){
    
    memset(h,-1,sizeof(h));
    cin>>n>>m>>cost>>edges;
    while(edges--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    int days;
    cin>>days;
    for(int i=1;i<=days;i++){
        int p,a,b;
        cin>>p>>a>>b;
        limit[p].push_back({a,b});
    }
    
    memset(f,0x3f,sizeof(f));
    f[0]=-cost;                            //注意这里的技巧,将之赋值为-cost以避免特判,因为第一次选路径是不用付cost的
    for(int i=1;i<=n;i++){                 //遍历每一天
        for(int j=i;j>=1;j--){             //注意枚举顺序,如果第j天及之后无法走某一航线,则对于任何一个小于j的天数k,第k天及之后也无法走此航线
            int distance=spfa(j,i);        //第j天到第i天用同一航线的最短路
            if(distance==inf) break;
            f[i]=min(f[i],f[j-1]+(i-j+1)*distance+cost);
        }
    }
    
    cout<<f[n];
    
    
    return 0;
    
}

你可能感兴趣的:(递推&动态规划)