题目
解法
设 \(dp_i\) 为推倒前 \(i\) 张骨牌的最小代价。考虑第 \(i\) 张骨牌向左推或不推(向右推实际上等价于后面的骨牌不推)。再设 \(l_i\) 为第 \(i\) 张骨牌向左推能推倒的最远骨牌,\(r_i\) 同理。
对于向左推,我们发现这相当于将 \([l_i,i]\) 中的骨牌都推倒了,所以转移直接从 \(l_i-1\) 继承:\(dp_i=dp_{l_i-1}+w_i\)。
对于不推,前面就必须有骨牌向右推倒,转移有:
\[dp_i=\min_{}\{f_{j-1}+w_j\ |\ j<i\le r_j\}
\]
但这是 \(\mathcal O(m^2)\) 的,用线段树优化也有一个 \(\log\),过不去。
可以发现,若骨牌 \(i\) 可以推倒骨牌 \(j\)(不妨设 \(i<j\)),那么向右推倒骨牌 \(i\) 延伸的长度一定大于 \(j\) 延伸的长度。
这和优化有什么关系呢?再清楚一点:假设能推倒 \(i\) 的骨牌集合为 \(S_i\),而 \(i\) 能推倒 \(j\),这就意味着 \(S_i\subseteq S_j\)。这说明合法推倒集合是可以继承的!我们考虑用单调栈。
首先根据这条法则,我们可以计算出 \(l_i,r_i\)。
对于不推的转移,这实际是一个取 \(S_i\) 中点 \(j\) 的 \(\min_{}\{f_{j-1}+w_j\}\) 的过程,用单调栈维护离 \(i\) 最近的能向右推倒它的点 \(k\),那么 \(S_i\) 就是 \(S_k\) 加上 \(k\) 这个单独元素,若令 \(pre_i\) 为 \(S_i\) 中点 \(j\) 的 \(\min_{}\{f_{j-1}+w_j\}\),实际上就是 \(dp_i=pre_{k}\)。
所以实际上并不需要维护 \(r_i\)。
代码
#include <stack>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
typedef long long ll;
typedef pair <int,ll> pii;
const int maxn=250005,maxl=1e7+2;
int n,m,l[maxl];
ll f[maxl],pre[maxl];
stack <int> s;
vector <pii> dom[maxn],d;
int main() {
n=read(9),m=read(9);
for(int i=1;i<=n;++i) {
int k=read(9);
for(int j=1;j<=k;++j)
dom[i].push_back(make_pair(read(9),0));
for(int j=1;j<=k;++j)
dom[i][j-1].second=read(9);
}
d.push_back(make_pair(1e8,0));
for(int q=read(9);q;--q) {
int id=read(9),mul=read(9);
for(int i=0;i<dom[id].size();++i)
d.push_back(make_pair(dom[id][i].first,dom[id][i].second*mul));
}
for(int i=m;i;--i) {
while(!s.empty() and i<=s.top()-d[s.top()].first)
l[s.top()]=i+1,s.pop();
s.push(i);
}
while(!s.empty()) s.pop();
for(int i=1;i<=m;++i)
if(!l[i]) l[i]=1;
pre[0]=1e18,s.push(0);
for(int i=1;i<=m;++i) {
while(!s.empty() and d[s.top()].first+s.top()<=i)
s.pop();
f[i]=min(f[l[i]-1]+d[i].second,pre[s.top()]);
pre[i]=min(pre[s.top()],f[i-1]+d[i].second);
s.push(i);
}
print(f[m],'\n');
return 0;
}