题目

传送门

解法

\(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;
}
posted on 2021-07-15 21:45  Oxide  阅读(33)  评论(0编辑  收藏  举报