题目简述:从左到右依次有 n ≤ 1 0 7 n≤10^7 n≤107个Domino骨牌,高度为 h i hi hi,手动推倒他的花费为 c i ci ci。每个骨牌之间的距离为 1 1 1。一个骨牌可以被向左或者向右推倒。当第 i i i个骨牌被推倒时,他会以相同方向推倒与其距离 < h i <hi <hi的所有骨牌。求推倒所有骨牌的最小花费。( P S : PS: PS:输入格式和题目描述相差甚远,及其恶心,不过我们能 i d e n t i f y w i t h h i s e f f o r t t o r e d u c e t h e I O t i m e \rm{identify \ with \ his \ effort \ to \ reduce \ the \ IO \ time} identify with his effort to reduce the IO time)
data structures dp two pointers *2900
实际上我们 O ( n 2 ) O(n^2) O(n2)算出每个点向左(右)推的最远距离然后就可以 O ( n 2 ) O(n^2) O(n2)DP了。
- 每个点向右推的最远距离如何 O ( n ) O(n) O(n)?
有一个很显然的性质:
假设 i i i向右推,能倒的最远的位置为 j j j
如果满足 j ≥ i + 1 j\geq i+1 j≥i+1,那么将 i + 1 i+1 i+1向右推,能倒的最远位置一定不超过 j j j
因为 i i i能推倒 i + 1 i+1 i+1,所以 i + 1 i+1 i+1倒了能推倒的位置i就一定能推倒。
发现如果我们把一个点和它往右推的最远位置连一条线,会是这个样子:
所以我们从左往右枚举点,每次把当前点加入单调栈中,如果当前点不能被栈顶点直接覆盖,那么栈顶点刚好不能覆盖当前点,最远覆盖为当前点-1,被弹出。
栈中相当于存着一层层包含,没有并列的弧,每次加入新弧,尝试弹出之前的弧并更新答案。
- 如何
O
(
n
)
O(n)
O(n)计算答案?
f [ i ] = min { f [ L [ i ] ] + c i , min j < i < R [ j ] { f [ j − 1 ] + c j } } f[i] = \min\left\{ f[L[i]]+c_i, \min_{j < i < R[j]} \{f[j-1]+c_j\} \right\} f[i]=min{f[L[i]]+ci,j<i<R[j]min{f[j−1]+cj}}
f [ L [ i ] ] + c i f[L[i]]+c_i f[L[i]]+ci很简单,看后面的。
现在我们要在上面那个图形中找出能推倒 i i i的点中的最小值。
发现能推倒 i i i的点的图案组成了一层层包含,没有并列的弧。
然后就直接单调栈维护,还同时维护一下栈内最小值(其实就是保证栈顶最小即可)就完了。
单调栈和单调队列,重要的是两单调
1:加入时间单调:队列的是队头最早,栈是栈顶最晚。
2:答案单调:取头一定最优,只是有可能不合法。
AC Code:
#include<bits/stdc++.h>
#define maxn 10000007
#define LL long long
using namespace std;
int n,m,siz[maxn],cnt,h[maxn],fl[maxn],fr[maxn];
LL c[maxn],f[maxn];
vector<int>H[maxn],C[maxn];
int q[maxn],R;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&siz[i]);H[i].resize(siz[i]),C[i].resize(siz[i]);
for(int j=0;j<siz[i];j++)
scanf("%d",&H[i][j]);
for(int j=0;j<siz[i];j++)
scanf("%d",&C[i][j]);
}
int Q;scanf("%d",&Q);
for(int i=1;i<=Q;i++){
int id,mul;scanf("%d%d",&id,&mul);
for(int j=0;j<siz[id];j++)
h[++cnt] = H[id][j] , c[cnt] = C[id][j] * 1ll * mul;
}
for(int i=1;i<=m;i++){
for(;R && i>=q[R-1]+h[q[R-1]];R--)
fr[q[R-1]] = i-1;
q[R++] = i;
}
for(;R;R--) fr[q[R-1]]=m;
for(int i=m;i>=1;i--){
for(;R && i<=q[R-1]-h[q[R-1]];R--)
fl[q[R-1]]=i+1;
q[R++]=i;
}
for(;R;R--) fl[q[R-1]]=1;
for(int i=1;i<=m;i++){
f[i] = f[fl[i]-1] + c[i];
for(;R && fr[q[R-1]]<i;R--);
if(R) f[i] = min(f[i] , f[q[R-1]-1]+c[q[R-1]]);
LL val = f[i-1] + c[i];
if((R && val<f[q[R-1]-1]+c[q[R-1]])|| !R)
q[R++] = i;
}
printf("%lld\n",f[m]);
}