link
單調棧+單調隊列+\(dp\)好題。數組
首先一眼看出是 \(dp\)。令 \(dp[i]\) 爲推倒前 \(i\) 個骨牌所耗費的最小价值,(這個並無單調性。
考慮 \(dp[i]\) 由什麼轉移來。數據結構
1.左邊的翻過來(至關於本身向右邊翻)。因爲能轉移過來的 \(j\) 並非連續的(顯然),因此在處理 \(dp[i]\) 時不是很好維護,不妨在處理 \(dp[j]\) 時更新 \(dp[i]\)。不妨設 \(i\) 向左翻能翻倒的最遠位置爲 \(L[i]\),向右爲 \(R[i]\)。一個很重要的性質:全部的 \([i,R[i]],[L[i],i]\) 要麼相離,要麼包含。
則 \(dp[i]=min(dp[i],dp[j-1]+a[j])(j \le i\le R[j])\)。ide
2.本身向左邊翻,則 \(dp[i]=min(dp[i],dp[j-1]+a[i])(L[i]\le j \le i)\)。
細心的讀者能夠發現,第 \(1\) 種狀況就是向右翻,因此轉移並無漏掉什麼。spa
你會發現以上全部的東西、全部的轉移均可以用線段樹/樹狀數組維護,這裏不展開。code
關於 \(L,R\) 數組的維護,乍一看用個單調棧/單調隊列。發現這題用普通的單調數據結構是沒有單調性的,可是爲了追求線性複雜度只能拋棄二分。可是這題有一個特殊的性質:若求 \(L[i]\) 時,當前 \(j>=i-h[i]\),那麼在單調棧/隊列中就能夠直接刪除(顯然),這就保證了線性複雜度,因爲 \(j\) 爲後進先出,用單調棧便可,求 \(R[i]\) 時同理。隊列
關於第 \(1\) 種狀況,單調隊列維護便可。注意這裏的 \(R[i]\) 雖然沒有單調性,可是有局部單調性,即對於相鄰的衆多區間,他們內部單降(由於全部的 \([i,R[i]]\) 要麼相離要麼包含)。因此可用單調棧維護。第 \(2\) 種能夠稍微偷懶一下,\(dp[i]=dp[L[i]-1]+a[i]\),能夠證實是最優的,本身yy一下吧。get
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <map> #include <queue> #define LL long long using namespace std; const int MAXN = 1e7 + 5, MAXM = 250005, inf = 0x3f3f3f3f; int m, n, k, tot, h[MAXN], que[MAXN], L[MAXN], R[MAXN], head, tail; int que1[MAXN], head1, tail1; vector <int> v1[MAXM], v2[MAXM]; LL a[MAXN], dp[MAXN]; void read(int &x) { x = 0; int f = 1; char c = getchar(); for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = 0; for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48); x = f ? x : -x; } int Min(int x, int y) { return x < y ? x : y; } int Max(int x, int y) { return x > y ? x : y; } LL MinLL(LL x, LL y) { return x < y ? x : y; } LL MaxLL(LL x, LL y) { return x > y ? x : y; } LL Calc(int x) { return a[x] + dp[x - 1]; } int main() { int x, y; read(m); read(n); for(int i = 1; i <= m; i ++) { read(k); for(int j = 1; j <= k; j ++) read(x), v1[i].push_back(x); for(int j = 1; j <= k; j ++) read(y), v2[i].push_back(y); } read(k); for(int i = 1; i <= k; i ++) { read(x); read(y); for(int j = 0; j < v1[x].size(); j ++) h[++ tot] = v1[x][j], a[tot] = (LL)v2[x][j] * y, h[tot] --;//, printf("|%d %lld\n", h[tot], a[tot]); } head = 1; tail = 0; n = tot; for(int i = 1; i <= n; i ++) { // 單調棧 L[i] = i; while(head <= tail && i - h[i] <= que[tail]) L[i] = Min(L[i], L[que[tail]]), tail --; que[++ tail] = i; } head = 1; tail = 0; for(int i = n; i >= 1; i --) { R[i] = i; while(head <= tail && i + h[i] >= que[tail]) R[i] = Max(R[i], R[que[tail]]), tail --; que[++ tail] = i; } // for(int i = 1; i <= n; i ++) printf("|%d %d|\n", L[i], R[i]); head = 1; tail = 0; dp[0] = 0; for(int i = 1; i <= n; i ++) { dp[i] = dp[L[i] - 1] + a[i]; while(tail1 && R[que1[tail1]] < i) tail1 --; // case1 que1[++ tail1] = i; while(tail1 > 1 && Calc(que1[tail1]) > Calc(que1[tail1 - 1])) tail1 --; if(tail1) dp[i] = MinLL(dp[i], Calc(que1[tail1])); // if(tail1) printf("|%d->%d %lld|\n", que1[tail1], i, MinLL(dp[i], Calc(que1[tail1]))); printf("%lld|", dp[i]); } printf("%lld", dp[n]); return 0; }