CF1131G Most Dangerous Shark

前言

提供一种不需要并查集的严格线性做法。

其实也差不了多少。

题目

题目链接

分析

题目描述稍微有点复杂,其实我们可以直接看成是这样的问题:

给定 \(m\) 个多米诺骨牌,每个有高度和价值,要求推倒所有的骨牌,求最小代价。(推动的规则见题面最后一段)

显然可以考虑 dp 来做,设 \(dp[i]\) 表示推倒前 \(i\) 个骨牌需要的最小代价。

为了描述的方便,我们设 \(L[i]\) 表示 \(i\) 往左最远可以推倒的点, \(R[i]\) 同理。

那么转移方程其实也很好写,考虑这样的决策点 \(j\) ,有两种情况。

第一种:(用 \(i\) 推倒 \([j,i]\) 区间的骨牌)

\[\large dp[i]=\min\{dp[j-1]+cost[i]\}\ \ \ \ (j\le L[i]\le i) \]

那么其实有一个显而易见的贪心就是每次直接让 \(i\)\(L[i]\) 处决策:

\[\large dp[i]=dp[L[i]-1]+cost[i] \]

接下来考虑第二种情况:(用 \(j\) 推倒 \([j,i]\) 区间的骨牌)

\[\large dp[i]=\min\{dp[j-1]+cost[j]\}\ \ \ \ (j<i\le R[j]) \]

这时我们发现,这个决策并不好做,因为即使我们求出了 \(R\) 数组,也需要使用数据结构维护后缀最小值,而不能做到线性复杂度。

那么接下来就是题目的性质了:

如果有 \(R[j]\ge i\ \ \ (j<i)\) ,那么一定有 \(R[j]\ge R[i]\) !(因为 \(j\) 可以推倒 \(i\)

根据上面的分析,我们可以得到这样的结论:

对于每一个 \([i,R[i]]\) 区间,要么较小的 \(i\) 对应区间包含另一个,要么互不相交。

也就是对于 \(i,j\ \ \ (i<j)\) ,其区间要么是 \([i,R[i]]\) 包含 \([j,R[j]]\) ,要么是 \(i\le R[i]<j\le R[j]\)

于是我们可以在这里考虑维护一个单调栈,按照 \(dp[j-1]+cost[j]\) 单调递增。

而此时,栈内的决策点也一定满足条件:\(R[i]\) 单调不降。(原因就是上面的性质,要么包含,不包含的直接在弹栈时弹出去了)

然后每次使用栈顶更新答案即可。(这个部分具体可以看代码最后一点的实现)

代码

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x){
	x=0;bool f=false;char ch=getchar();
	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
	while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
	x=f?-x:x;
	return ;
}
template<typename T>
inline void write(T x){
	if(x<0) x=-x,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10^48);
	return ;
}
const int N=3e5+5,M=1e7+5;
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define mp make_pair
#define ll long long
int n,m,k,h[M],cnt;
vector<ll>a[N],b[N];
int que[M],hh=1,tt,L[M],R[M];
ll dp[M],v[M];
signed main(){
	read(n),read(m);
	for(register int i=1,len,x;i<=n;i++){
		read(len);
		for(register int j=1;j<=len;j++) read(x),a[i].pb(x);
		for(register int j=1;j<=len;j++) read(x),b[i].pb(x);
	}
	read(k);
	for(register int i=1,id,mul;i<=k;i++){
		read(id),read(mul);
		const int len=a[id].size();
		for(register int j=0;j<len;j++) h[++cnt]=a[id][j],v[cnt]=b[id][j]*mul;
	}
	for(register int i=1;i<=cnt;i++){//求 L 数组
		while(hh<=tt&&i-h[i]<=que[tt]-h[que[tt]]) tt--;
		if(hh>tt||i-h[i]>=que[tt]) L[i]=max(1,i-h[i]+1);
		else L[i]=L[que[tt]];
		que[++tt]=i;
	}
	hh=1,tt=0;
	for(register int i=cnt;i>=1;i--){//求 R 数组
		while(hh<=tt&&i+h[i]>=que[tt]+h[que[tt]]) tt--;
		if(hh>tt||i+h[i]<=que[tt]) R[i]=min(cnt,i+h[i]-1);
		else R[i]=R[que[tt]];
		que[++tt]=i;
	}
	tt=0;
	for(register int i=1;i<=cnt;i++){//dp转移
		dp[i]=dp[L[i]-1]+v[i];//第一种情况
		while(tt&&R[que[tt]]<i) tt--;//查看当前状态到底是包含还是相离
		if(tt) dp[i]=min(dp[i],dp[que[tt]-1]+v[que[tt]]);//如果是包含
		if(!tt||dp[i-1]+v[i]<dp[que[tt]-1]+v[que[tt]]) que[++tt]=i;//如果栈内没有元素或者有更好的决策点
	}
        //可以发现,每时每刻其实都保证了栈内决策点的 R 一定单调不降!
	write(dp[cnt]);
	return 0;
}
posted @ 2021-09-13 18:52  __Anchor  阅读(25)  评论(0编辑  收藏  举报