http://codeforces.com/gym/100801
题目大意:有从左到右有n个车站,有n-1种车票,第i种车票一次最多可以坐 i 站(1<=i<=n) 每种票有固定的价钱p[i],下车后可以继续上车,但每次下车都要再上车都要花费一定时间v[i],第1和n站上车不花费时间,车从一站到相邻的一站需要1分钟。只能买一种车票,问在t时间内 所有能从第1站到 第n站的方案 花费 的票价最低是多少。
解题思路:
这道题的关键在于如何求每种票起点到达终点所需的最少时间,如果找出了每种票到车站的最少时间,再直接判断哪种票最便宜就好了。
对于求每种票的时间,最直观的想法是对每一种票进行dp,设dp[i]为到i站所需的最少时间,k为此票最多能乘坐的站数,v[i]为在此站下车又上车的时间。
dp[i]=min(dp(i-j))+v[i] 1<=j<=i-k
对于n-1种票,每种票枚举n个车站,枚举到每个车站时还要向前推k个,复杂度为O(n^3)
但我们会发现,如果 坐n站的车票,能够在t时间内到达 。那么 坐n+1站的车票,也能够在t时间内到达。 这样我们就可以二分查找所需时间小于等于t-(n-1)的车票中能坐站数最少的车票就好了。(规定时间内能够到达的车票分界线)复杂度n^2*logn 还是会超时
对于每次dp,我们观察状态方程发现最佳状态是找dp[i-j](1<=j<=i-k)区间中的最小值,每次查找时间是o(k) 。其实我们可以维护一个长度为k的单调队列来找最小值。复杂度o(n*logn) ,解决。
AC代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn = 1e5+10 ,mod = 998244353,inf=0x3f3f3f3f; 4 const double pi=acos(-1.0); 5 typedef long long ll; 6 ll dp[maxn]; 7 ll p[maxn],v[maxn],n,m; 8 struct node 9 { 10 ll val,pos; 11 }q[maxn]; 12 ll solve(ll k) 13 { 14 memset(dp,inf,sizeof(dp)); 15 ll head=1,tail=0; 16 for(ll i=1;i<=k;i++) //前k个的最优解就是v[i] 17 { 18 dp[i]=v[i]; 19 while(head<=tail&&dp[i]<=q[tail].val)tail--; 20 q[++tail].val=dp[i],q[tail].pos=i; 21 } 22 //cout<<tail<<endl; 23 for(ll i=k;i<=n;i++) 24 { 25 while(i-q[head].pos>k)head++; //注意下标范围 26 dp[i]=min(dp[q[head].pos]+v[i],dp[i]); 27 while(head<=tail&&dp[i]<=q[tail].val)tail--; //维护单调性 28 q[++tail].val=dp[i],q[tail].pos=i; 29 //for(int j=1;j<=k;j++) //未优化之前代码 30 // { 31 // if(i-j>0) 32 // dp[i]=min(dp[i-j]+v[i],dp[i]); 33 // } 34 } 35 return dp[n]; 36 } 37 int main() 38 { 39 freopen("journey.in","r",stdin); //交题要用到文件流 40 freopen("journey.out","w",stdout); 41 scanf("%lld %lld",&n,&m); 42 for(ll i=1;i<=n-1;i++) 43 scanf("%lld",&p[i]); 44 v[1]=v[n]=0; 45 for(ll i=2;i<=n-1;i++) 46 scanf("%lld",&v[i]); 47 m-=n-1; 48 ll l=1,r=n-1; 49 while(l<=r) //二分 50 { 51 ll mid=(l+r)/2; 52 if(solve(mid)>m) 53 l=mid+1; 54 else 55 r=mid-1; 56 } 57 ll ans=inf; 58 for(ll i=l;i<=n-1;i++) //找到分界点从右边区间找最小值 59 { 60 ans=min(ans,p[i]); 61 } 62 printf("%lld ",ans); 63 }