传送门 http://codeforces.com/problemset/problem/721/C
题目大意:
有一个dag图,代表一个城市。城市里有若干景点,以及连接景点的单向道路,每条道路都有一个通过的时间ti。
你从景点1出发,到景点n,在有限的时间T内,至多能游览几个景点?
题目分析:
一开始想的是类似于背包的思路,即令
dp[i][t]
代表从1点走到i点,不超过时间t,能游览多少个景点,然后最终答案是
dp[n][T]
.但题目中T的范围高达
109
,所以考虑从前向后递推。
令
dp[i][j]
代表从i点走到n点,途径j个点的最小时间,那么要想从i点走到n点,必须经过i点的后继结点,且要选取局部最小花销的点去走,所以很显然满足DP的性质。
因此
dp[i][j]=min{dp[k][j−1]+dist[i][k]},k∈i的后继结点集合
。
而题目中要求输出游览的路径,所以每次更新dp[i][j],还要维护一个to[i][j],代表从i点经j步走到n点的路线中,下一个要走的点。
一开始这个数组起的名字是next[i][j],但是一直抛compilation error,百思不得其解。结果是跟头文件某处出现的next指针重名了。。。。。
这里有一个坑,就是记录距离不可使用邻接矩阵,否则会爆内存。
第一次遇到图上的DP,需要谨记此种思路,其实跟树形DP差不多,就是要加visited标记。DP的水还是深啊,微软第二题那道DP就死活想错了。。。。。。 传说,acm中一半的题都跟DP有关系……?
#include <bits/stdc++.h>
using namespace std;
vector<int> g[5005];
vector<int> dist[5005];
short to[5005][5005];
int dp[5005][5005];
bool vis[5005];
int n,m,t;
void dfs(int c) { // depth-first search from node c
vis[c]=true;
if(c==n)
return;
for(int i=0;i<g[c].size();i++) {
int k=g[c][i],d=dist[c][i];
if(!vis[k])
dfs(k);
for(int j=2;j<=n;j++) {
if(dp[k][j-1]+d<dp[c][j]) {
dp[c][j]=dp[k][j-1]+d;
to[c][j]=k;
}
}
}
}
int main() {
memset(vis,0,sizeof(vis));
memset(dist,0,sizeof(dist));
memset(to,0,sizeof(to));
memset(dp,0x3f,sizeof(dp));
scanf("%d %d %d",&n,&m,&t);
int u,v,d;
for(int i=0;i<m;i++) {
scanf("%d %d %d",&u,&v,&d);
g[u].push_back(v);
dist[u].push_back(d);
}
dp[n][1]=0;
dfs(1);
int a=0;
for(int i=n;i>=1;i--) {
if(dp[1][i] <= t) {
printf("%d\n", i);
printf("1 ");
a=i;
break;
}
}
int j=1;
while(a>1) {
if(a!=1)
printf("%d ", to[j][a]);
else
printf("%d", to[j][a]);
j=to[j][a--];
}
}