[LOJ539] 旅游路线

Description

依然懒得概括题面

T 城是一个旅游城市,具有 \(n\) 个景点和 \(m\) 条道路,所有景点编号为 \(1,2,...,n\)。每条道路连接这 \(n\) 个景区中的某两个景区,道路是单向通行的。每条道路都有一个长度。

为了方便旅游,每个景点都有一个加油站。第 \(i\) 个景点的加油站的费用为 \(p_i\),加油量为 \(c_i\)。若汽车在第 \(i\) 个景点加油,则需要花费 \(p_i\) 元钱,之后车的油量将被加至油量上限与 \(c_i\) 中的较小值。不过如果加油前汽车油量已经不小于 \(c_i\),则不能在该景点加油。

小 C 准备来到 T 城旅游。他的汽车油量上限为 \(C\)。旅游开始时,汽车的油量为 \(0\)。在旅游过程中:

1、当汽车油量大于 \(0\) 时,汽车可以沿从当前景区出发的任意一条道路到达另一个景点(不能只走道路的一部分),汽车油量将减少 \(1\)

2、当汽车在景点 \(i\) 且当前油量小于 \(c_i\) 时,汽车可以在当前景点加油,加油需花费 \(p_i\) 元钱,这样汽车油量将变为 \(\min\{c_i,C\}\)

一次旅游的总花费等于每次加油的花费之和,旅游的总路程等于每次经过道路的长度之和。注意多次在同一景点加油,费用也要计算多次,同样地,多次经过同一条道路,路程也要计算多次。

小 C 计划旅游 \(T\) 次,每次旅游前,小 C 都指定了该次旅游的起点和目标路程。由于行程不同,每次出发前带的钱也不同。为了省钱,小 C 需要在旅游前先规划好旅游路线(包括旅游的路径和加油的方案),使得从起点出发,按照该旅游路线旅游结束后总路程不小于目标路程,且剩下的钱尽可能多。请你规划最优旅游路线,计算这 \(T\) 次旅游每次结束后最多可以剩下多少钱。

Solution

发现这种真正可以出到\(noip\)里的题的题解都是根据数据范围和特殊数据一点一点改进算法最后出正解的。考场上如果不能一下想到正解的话就可以根据数据范围一点点往下做。

提前处理\(c_i=min(c_i,C)\)

数据范围内有个很隐晦的提示是\(1\leq q_i\leq n^2\),也就是说每次花的钱\(\leq 10000\)

可以根据这个设计状态\(f(i,c,q)\)表示当前在点\(i\),手里有\(q\)元钱,车里还有\(c\)升油时最远能走多远。

那么转移可以枚举出边以及当前点加不加油

\[f(i,c,q)=\max\begin{cases}f(b_j,c-1,q)+l_j,&a_j=i,&\mathrm{if\ }c>0\\f(i,c_i,q-p_i),&&\mathrm{if\ }c<c_i\land q\ge p_i\end{cases}\]

状态数有点大,考虑缩减状态

发现在一个点加完油之后的油量是确定的,不用在状态中记录当前油量。\(f(i,q)\)表示保证在点\(i\)加了油,手里还有\(q\)元钱最远能走多远,然后枚举下个加油点\(j\),转移就是

\[f(i,q)=\max\left \{ f(j,q-p_i)+w(i,j,c_i) \right\}\]

其中\(w(i,j,c)\)表示从\(i\)\(j\)经过不超过\(c\)条道路的最大路程。这个可以用\(DP\)预处理,枚举\(i\)的出边\(t\)

\[w(i,j,c)=\max\left\{w(t,j,c-1)+l_{i,j}\right\}\]

边界是 \(w(i,i,0)=0,w(i,j,0)=-\infty\) (\(i\ne j\))。

时间复杂度 \(O(n^4+nmC+T\log n^2)\),期望得分\(75\)

上述算法的复杂度瓶颈为预处理\(w(i,j,c)\)。观察到每次转移\(c\)减少\(1\),可以用倍增加速预处理过程。

\(g(i,j,k)\)表示从\(i\)\(j\)经过不超过\(2^k\)条道路的最大路程。转移就是

\[g(i,j,k)=\max\limits_{t}\left\{g(i,t,k-1)+g(t,j,k-1)\right\}\]

那只需要把所有\(i,j\)都处理出\(w(i,j,c_i)\)即可,每次提取出\(c\)的一个二进制位\(2^k\),那么

\[w(i,j,c)=\max\limits_x\{w(i,x,c-2^k)+g(x,j,k)\}\]

倍增处理\(\log C\)轮即可。

时间复杂度\(O(n^4+nm\log C+T\log n^2)\)

注意到处理\(w(i,j,c)\)时如果还需要去求\(w(i,x,c-2^k)\)那空间复杂度爆炸,可以优化这个过程,就像倍增\(Floyd\)一样,如果\(c_i\)的第\(k\)位为\(1\),那就尝试将\(w\)数组和\(g(*,*,k)\)合并。注意合并的时候要开一个临时数组不然会\(WA\)

求完\(w\)数组之后求出\(f\)数组,然后每次询问二分即可。

标准\(noip\)

Code

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<cctype>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using std::min;
using std::max;
using std::swap;
using std::vector;
const int N=105;
typedef double db;
typedef long long ll;
#define pb(A) push_back(A)
#define pii std::pair<int,int>
#define all(A) A.begin(),A.end()
#define mp(A,B) std::make_pair(A,B)

int n,m,C,T;
int mp[N][N]; 
int p[N],c[N];
int f[N][N*N];
int val[N][N];
int w[N],sy[N];
int g[N][N][21];

int getint(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch))w|=ch=='-',ch=getchar();
    while( isdigit(ch))X=X*10+ch-48,ch=getchar();
    if(w) return -X;return X;
}

signed main(){
    n=getint(),m=getint(),C=getint(),T=getint();
    for(int i=1;i<=n;i++){
        p[i]=getint();
        c[i]=getint();
        c[i]=min(c[i],C);
    } memset(g,0xcf,sizeof g);
    for(int i=1;i<=n;i++) g[i][i][0]=0;
    for(int i=1;i<=m;i++){
        int x=getint(),y=getint(),z=getint();
        g[x][y][0]=max(g[x][y][0],z);
    }
    for(int p=1;(1<<p)<=C;p++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++)
                    g[i][j][p]=max(g[i][j][p],g[i][k][p-1]+g[k][j][p-1]);
            }
        }
    }
    for(int x=1;x<=n;x++){
        memset(w,0xcf,sizeof w);w[x]=0;
        for(int i=0;(1<<i)<=c[x];i++){
            if(c[x]>>i&1){
                memcpy(sy,w,sizeof w);
                for(int j=1;j<=n;j++)
                    for(int p=1;p<=n;p++)
                        sy[j]=max(sy[j],w[p]+g[p][j][i]);
                memcpy(w,sy,sizeof sy);
            }
        } memcpy(val[x],w,sizeof w);
    }
    for(int i=0;i<=n*n;i++){
        for(int x=1;x<=n;x++){
            if(i>=p[x]){
                for(int j=1;j<=n;j++) 
                    f[x][i]=max(f[x][i],f[j][i-p[x]]+val[x][j]);
            }
        }
    }
    while(T--){
        int s=getint(),q=getint(),d=getint();
        int l=0,r=q,ans=q+1;
        while(l<=r){
            int mid=l+r>>1;
            if(f[s][mid]>=d) ans=mid,r=mid-1;
            else l=mid+1;
        } printf("%d\n",max(-1,q-ans));
    } return 0;
}

猜你喜欢

转载自www.cnblogs.com/YoungNeal/p/9898061.html
今日推荐