1. 程式人生 > >【題解】Atcoder ARC#67 F-Yakiniku Restaurants

【題解】Atcoder ARC#67 F-Yakiniku Restaurants

  覺得我的解法好簡單,好優美啊QAQ

  首先想想暴力怎麼辦。暴力的話,我們就列舉左右端點,然後顯然每張購物券都取最大的值。這樣的複雜度是 \(O(n ^{2} m)\) 的。但是這樣明顯能夠感覺到我們重複計算了很多東西,因為區間 \((l, r)\) 的答案與區間 \((l + 1, r)\) 的答案並不是獨立的。

  我們可以考慮一下掃描線的做法。用一根掃描線從右往左掃左端點,同步維護所有以 \(l\) 為左端點的區間。由於我們現已經求出了所有以 \(l + 1\) 為左端點的區間答案(這裡的答案指從 \(l -> r\) 中吃東西所能獲得的最大權值),我們可以求出 \(l + 1, r\) 到 \(l, r\) 的增量變化,那麼 \(ans[l][r] = ans[l + 1][r] + t\)。

  這個答案的增量顯然只與 \(l\) 端點所能獲得的權值有關。考慮第 j 個購物券,我們可以維護一個值單調遞增的單調棧表示在每一個地點使用 j 購物券能獲得最大權值的區間。彈棧的時候,我們用 \(val[i][j] - S[j][top].num\) 即可求出增量。這個增量會增加在 \(ans[i][j] -> ans[i][k]\) 這樣的一個區間中。差分就可以解決了。

  感覺自己講起來好混亂啊……すみません……

#include <bits/stdc++.h>
using namespace std;
#define maxn 5005
#define int long long
#define
maxm 250 int n, m, dis[maxn], val[maxn][maxm]; int Ans, ans[maxn][maxn], Q[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * k; } struct node { int num, id; node(int _id = 0, int _num = 0) { num = _num, id = _id; } }S[maxm][maxn]; signed main() { n = read(), m = read(); for(int i = 2; i <= n; i ++) dis[i] = read() + dis[i - 1]; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) val[i][j] = read(); for(int i = 1; i <= m; i ++) S[i][0].id = n + 1; for(int i = n; i >= 1; i --) { for(int j = 1; j <= m; j ++) { int top = Q[j]; ans[i][i] += val[i][j]; ans[i][i + 1] -= val[i][j]; while(top && S[j][top].num <= val[i][j]) { int l = S[j][top].id, r = S[j][top - 1].id; int t = val[i][j] - S[j][top].num; ans[i][l] += t, ans[i][r] -= t; top --; } S[j][++ top] = node(i, val[i][j]); Q[j] = top; } } for(int i = n; i; i --) { for(int j = i; j <= n; j ++) ans[i][j] += ans[i][j - 1]; for(int j = i; j <= n; j ++) ans[i][j] += ans[i + 1][j]; for(int j = i; j <= n; j ++) Ans = max(Ans, ans[i][j] - dis[j] + dis[i]); } printf("%lld\n", Ans); return 0; }