一、题目
二、解法
考虑每个点的推倒状态只有向左倒和向右倒,然后我们又要把所有骨牌推倒,所以设计 (dp[i]) 表示推倒前 (i) 个骨牌的最小代价,假设我们会处理 (l[i],r[i]) 表示向左推倒骨牌 (i) 覆盖的左端点,向右推倒骨牌 (i) 覆盖的右端点,转移:
-
把 (i) 向左推倒:(dp[i]=dp[j]+c[i] l[i]-1leq j<i)
-
把 (j) 向右推倒:(dp[i]=dp[j-1]+c[j] j<ileq r[j])
第一种转移其实只需要考虑 (j=l[i]-1) 时的转移即可,因为 ([l[i],i]) 的骨牌 (i) 能推倒就不需要前面去管了。第二种转移看似需要线段树优化,做到 (O(n)) 我们需要观察性质。
不难发现每个骨牌的覆盖范围要么包含,要么相离,所以可以维护一个栈,每次新加入的点就放在栈顶,每次只需要考虑栈顶得覆盖范围够不够,不够直接弹出,然后维护一个栈的前缀最小值就可以转移了。
现在的问题是算 (l[i],r[i]),就说 (l[i]) 怎么算吧,还是维护一个栈,首先弹出没有用的点,也就是 (i-h[i]leq tp-h[tp]),并且栈顶对于以后的元素也没有用了。然后我们考虑当前点能不能覆盖到栈顶,如果不能的话就直接用 (i-h[i]+1),如果可以的话就用 (l[tp]),不难发现他是最优的。
三、总结
考虑每个点的状态有哪些,然后 (dp),只要涉及了所有状态就可以。
(dp) 的线性优化可以考虑找性质,然后用单调数据结构维护该性质。
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 10000005;
const int N = 250005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,q,h[M],c[M],f[M],l[M],r[M],st[M],mn[M];
vector<int> a[N],b[N];
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
int k=read();
for(int j=1;j<=k;j++) a[i].push_back(read());
for(int j=1;j<=k;j++) b[i].push_back(read());
}
q=read();n=0;
while(q--)
{
int id=read(),ml=read();
for(int i=0;i<a[id].size();i++)
h[++n]=a[id][i],c[n]=b[id][i]*ml;
}
int qr=0;
for(int i=1;i<=n;i++)
{
while(qr && i-h[i]<=st[qr]-h[st[qr]]) qr--;//useless
if(!qr || i-h[i]>=st[qr]) l[i]=max(i-h[i]+1,1ll);
else l[i]=l[st[qr]];
st[++qr]=i;
}
qr=0;
for(int i=n;i>=1;i--)
{
while(qr && i+h[i]>=st[qr]+h[st[qr]]) qr--;
if(!qr || i+h[i]<=st[qr]) r[i]=min(i+h[i]-1,m);
else r[i]=r[st[qr]];
st[++qr]=i;
}
qr=0;mn[0]=1e18;
for(int i=1;i<=n;i++)
{
while(qr && r[st[qr]]==i-1) qr--;//illegal
f[i]=f[l[i]-1]+c[i];
if(qr) f[i]=min(f[i],mn[qr]);
st[++qr]=i;mn[qr]=min(mn[qr-1],f[i-1]+c[i]);
}
printf("%lld
",f[n]);
}