Link
dp,离散化, 2200
题意
有一栋 n n n 层高楼,每层有 m m m 个房间,现在需要从 ( 1 , 1 ) → ( n , m ) (1,1) \rightarrow (n, m) (1,1)→(n,m),同层间向左or右移动需要消耗 x i x_i xi 点体力(每层对应一个 x i x_i xi ),除此之外有 k k k 个梯子,可以从 ( i , j ) → ( p , q ) (i, j)\rightarrow (p, q) (i,j)→(p,q) 并恢复 k i k_i ki 点体力,保证 i < p i < p i<p,问到达终点所需的最少体力,或无法到达。 n , m , k ≤ 1 e 5 n,m,k \leq 1e5 n,m,k≤1e5
思路
由于
n
,
m
≤
1
e
5
n, m \leq 1e5
n,m≤1e5,所以如果直接做的话会t掉,注意到移动过程中楼层不会减少,所以更新时一定是从低层向高层更新。
接下来的问题是如何更新同一层中的点。由于答案只和梯子连接的最多
2
∗
k
2*k
2∗k以及起点终点即最多
2
∗
k
+
2
2*k+2
2∗k+2个点有关,注意到
k
≤
1
e
5
k \leq 1e5
k≤1e5,先把同一层的点离散化,并按y从小到大排序,相邻之间只需要
O
(
1
)
O(1)
O(1) 转移,正反各扫一遍取最小值。
再对于该层的楼梯起点,更新楼梯终点的dp值。这一步更新的时候一定要保证该起点可达,否则由于起点到终点是负值,可能导致起点不可达但终点也被更新了
代码
const int maxn = 1e5 + 10;
const int maxm = 2e5 + 10;
const int N = 1e3 + 10;
const int P = 998244353; //998244353
// const int INF = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double EPS=1e-6;
vector<pii > row[maxn];
ll dp[maxm];
pii to[maxm];
int x[maxn];
int n, m, k;
void solve() {
cin >> n >> m >> k;
for(int i = 1; i <= 2 * k + 2; i++) {
dp[i] = INF;
}
for(int i = 1; i <= n; i++) {
row[i].clear();
}
for(int i = 1; i <= n; i++) cin >> x[i];
int cnt = 0;
dp[1] = 0;
row[1].push_back(make_pair(1, ++cnt));
for(int i = 1; i <= k; i++) {
int x, y, x1, y1, h;
cin >> x >> y >> x1 >> y1 >> h;
row[x].push_back(make_pair(y, ++cnt));
row[x1].push_back(make_pair(y1, ++cnt));
to[cnt - 1] = make_pair(cnt, h);
}
row[n].pb(mp(m, ++cnt));
for(int i = 1; i <= n; i++) {
sort(row[i].begin(), row[i].end());
for(int j = 1; j < row[i].size(); j++) {
dp[row[i][j].se] = min(dp[row[i][j].se], dp[row[i][j-1].se] + 1ll*x[i]*(row[i][j].fi - row[i][j-1].fi));
}
for(int j = row[i].size() - 2; j >= 0; j--) {
dp[row[i][j].se] = min(dp[row[i][j].se], dp[row[i][j+1].se] + 1ll*x[i]*(row[i][j+1].fi - row[i][j].fi));
}
for(auto j : row[i]) {
if(dp[j.se] != INF && to[j.se].fi)
dp[to[j.se].fi] = min(dp[to[j.se].fi], dp[j.se] - 1ll*to[j.se].se);
}
}
if(dp[cnt] != INF) {
cout << dp[cnt] << endl;
}
else {
// puts("NO ESCAPE");
cout << "NO ESCAPE\n";
}
}