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]} (jle 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<ile 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]]) ,要么是 (ile R[i]<jle 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;
}
原文地址:https://www.cnblogs.com/Akmaey/p/15264367.html