[DP 倍增Floyd] LOJ#539.「LibreOJ NOIP Round #1」旅游路线

本文介绍了一种使用DP算法解决特定问题的方法。通过定义状态f_{i,j}

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不算很难想。首先看到总钱数比较少,考虑 DPfi,j 表示从 i 出发,已经在 i 加了油,带了 j 块钱,能走多远。
考虑如何转移,注意到油量比较大,所有不可能把它记到状态里。那我们就枚举下一次在哪里加油:

fi,j=maxk{fk,jpj+gi,k}

其中 gi,k 表示从 i 走到 k,至多走 min{C,ci} 条边的最长路。
现在我们只需解决怎么预处理出 g 数组。
如果暴力把步数记到状态里 DP ,复杂度是 O(n(n+m)C),就可以有75分。
正解是倍增 floyd ,预处理出 hk,i,j 表示从 i 走到 j ,至多走 2k 步的最长路,然后就能 O(n3logC) 求了。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<queue>
#define Fir first
#define Sec second
#define mp(x,y) make_pair(x,y)
using namespace std;
inline char gc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int getint(){
    char ch=gc(); int res=0,ff=1;
    while(!isdigit(ch)) ch=='-'?ff=-1:0, ch=gc();
    while(isdigit(ch)) res=(res<<3)+(res<<1)+ch-'0', ch=gc();
    return res*ff;
}
const int maxn=102,maxc=1005,maxe=1005;
int n,m,C,p[maxn],c[maxn],_test,ans;
int fir[maxn],w[maxe],nxt[maxe],son[maxe],tot;
void add(int x,int y,int z){
    son[++tot]=y; w[tot]=z; nxt[tot]=fir[x]; fir[x]=tot;
}
int g[maxn][maxn],h[18][maxn][maxn],A[maxn],B[maxn];
int f[maxn][maxn*maxn],INF;

inline void DP(int S){
    memset(A,192,sizeof(A)); A[S]=0;
    for(int i=0;i<=16;i++) if((min(C,c[S])>>i)&1){
        memset(B,192,sizeof(B));
        for(int j=1;j<=n;j++) if(A[j]>INF)
         for(int k=1;k<=n;k++) if(h[i][j][k]>INF)
          B[k]=max(B[k],A[j]+h[i][j][k]);
        memcpy(A,B,sizeof(B));
    }
    for(int i=1;i<=n;i++) g[S][i]=A[i];
}
int main(){
    n=getint(); m=getint(); C=getint(); _test=getint();
    for(int i=1;i<=n;i++) p[i]=getint(), c[i]=getint();
    memset(h,192,sizeof(h)); INF=h[0][0][0];
    for(int i=1;i<=n;i++) h[0][i][i]=0;
    for(int i=1;i<=m;i++){
        int x=getint(),y=getint(),z=getint();
        add(x,y,z);
        h[0][x][y]=max(h[0][x][y],z);
    }
    for(int k=1;k<=16;k++){
        memcpy(h[k],h[k-1],sizeof(h[k]));
        for(int l=1;l<=n;l++)
         for(int i=1;i<=n;i++)
          for(int j=1;j<=n;j++) if(h[k-1][i][l]>INF&&h[k-1][l][j]>INF)
            h[k][i][j]=max(h[k][i][j],h[k-1][i][l]+h[k-1][l][j]);
    }
    for(int i=1;i<=n;i++) DP(i);

    for(int j=0;j<=n*n;j++)
    for(int i=1;i<=n;i++) for(int k=1;k<=n;k++) f[i][j]=max(f[i][j],g[i][k]);
     for(int j=1;j<=n*n;j++)
      for(int i=1;i<=n;i++) // f[i][j] µ±Ç°ÔÚi,ÒѾ­Ô­µØ¼ÓÁËÓÍ£¬ Ê£ÏÂj¿éÇ® 
       for(int k=1;k<=n;k++)
        if(j>=p[k]) f[i][j]=max(f[i][j],f[k][j-p[k]]+g[i][k]);

    while(_test--){
        int s=getint(),q=getint(),d=getint();
        ans=-1;
        int L=p[s],R=q;
        while(L<=R){
            int mid=(L+R)>>1;
            if(f[s][mid-p[s]]>=d) R=mid-1, ans=q-mid;
                        else L=mid+1;
        }
        printf("%d\n",max(-1,ans)); 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值