[多米诺骨牌单调栈模型]CodeForces 1131G. Most Dangerous Shark

130 篇文章 1 订阅
105 篇文章 0 订阅
该博客详细介绍了如何利用单调栈解决多米诺骨牌推倒问题,讨论了如何在O(n^2)的时间复杂度内计算每个点向左右推的最远距离,并提出一种O(n)的解决方案,通过维护单调栈来找到能推倒当前点的点中的最小值,从而得到最小总花费。
摘要由CSDN通过智能技术生成

题目

题目简述:从左到右依次有 n ≤ 1 0 7 n≤10^7 n107个Domino骨牌,高度为 h i hi hi,手动推倒他的花费为 c i ci ci。每个骨牌之间的距离为 1 1 1。一个骨牌可以被向左或者向右推倒。当第 i i i个骨牌被推倒时,他会以相同方向推倒与其距离 &lt; h i &lt;hi <hi的所有骨牌。求推倒所有骨牌的最小花费。( P S : PS: PS:输入格式和题目描述相差甚远,及其恶心,不过我们能 i d e n t i f y   w i t h   h i s   e f f o r t   t o   r e d u c e   t h e   I O   t i m e \rm{identify \ with \ his \ effort \ to \ reduce \ the \ IO \ time} identify with his effort to reduce the IO time)

data structures      dp      two pointers      *2900

实际上我们 O ( n 2 ) O(n^2) O(n2)算出每个点向左(右)推的最远距离然后就可以 O ( n 2 ) O(n^2) O(n2)DP了。

  1. 每个点向右推的最远距离如何 O ( n ) O(n) O(n)?

有一个很显然的性质:
假设 i i i向右推,能倒的最远的位置为 j j j
如果满足 j ≥ i + 1 j\geq i+1 ji+1,那么将 i + 1 i+1 i+1向右推,能倒的最远位置一定不超过 j j j
因为 i i i能推倒 i + 1 i+1 i+1,所以 i + 1 i+1 i+1倒了能推倒的位置i就一定能推倒。

发现如果我们把一个点和它往右推的最远位置连一条线,会是这个样子:
(是不是很形象?)

所以我们从左往右枚举点,每次把当前点加入单调栈中,如果当前点不能被栈顶点直接覆盖,那么栈顶点刚好不能覆盖当前点,最远覆盖为当前点-1,被弹出。
栈中相当于存着一层层包含,没有并列的弧,每次加入新弧,尝试弹出之前的弧并更新答案。

  1. 如何 O ( n ) O(n) O(n)计算答案?
    f [ i ] = min ⁡ { f [ L [ i ] ] + c i , min ⁡ j &lt; i &lt; R [ j ] { f [ j − 1 ] + c j } } f[i] = \min\left\{ f[L[i]]+c_i, \min_{j &lt; i &lt; R[j]} \{f[j-1]+c_j\} \right\} f[i]=min{f[L[i]]+ci,j<i<R[j]min{f[j1]+cj}}
    f [ L [ i ] ] + c i f[L[i]]+c_i f[L[i]]+ci很简单,看后面的。
    现在我们要在上面那个图形中找出能推倒 i i i的点中的最小值。
    发现能推倒 i i i的点的图案组成了一层层包含,没有并列的弧。
    然后就直接单调栈维护,还同时维护一下栈内最小值(其实就是保证栈顶最小即可)就完了。

单调栈和单调队列,重要的是两单调
1:加入时间单调:队列的是队头最早,栈是栈顶最晚。
2:答案单调:取头一定最优,只是有可能不合法。

AC Code:

#include<bits/stdc++.h>
#define maxn 10000007
#define LL long long
using namespace std;

int n,m,siz[maxn],cnt,h[maxn],fl[maxn],fr[maxn];
LL c[maxn],f[maxn];
vector<int>H[maxn],C[maxn];
int q[maxn],R;

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&siz[i]);H[i].resize(siz[i]),C[i].resize(siz[i]);
		for(int j=0;j<siz[i];j++)
			scanf("%d",&H[i][j]);
		for(int j=0;j<siz[i];j++)
			scanf("%d",&C[i][j]);
	}
	int Q;scanf("%d",&Q);
	for(int i=1;i<=Q;i++){
		int id,mul;scanf("%d%d",&id,&mul);
		for(int j=0;j<siz[id];j++)
			h[++cnt] = H[id][j] , c[cnt] = C[id][j] * 1ll * mul;
	}
	for(int i=1;i<=m;i++){
		for(;R && i>=q[R-1]+h[q[R-1]];R--)
			fr[q[R-1]] = i-1;
		q[R++] = i;
	}
	for(;R;R--) fr[q[R-1]]=m;
	for(int i=m;i>=1;i--){
		for(;R && i<=q[R-1]-h[q[R-1]];R--)
			fl[q[R-1]]=i+1;
		q[R++]=i;
	}
	for(;R;R--) fl[q[R-1]]=1;
	for(int i=1;i<=m;i++){
		f[i] = f[fl[i]-1] + c[i];
		for(;R && fr[q[R-1]]<i;R--);
		if(R) f[i] = min(f[i] , f[q[R-1]-1]+c[q[R-1]]);
		LL val = f[i-1] + c[i];	
		if((R && val<f[q[R-1]-1]+c[q[R-1]])|| !R)
			q[R++] = i;
	}
	printf("%lld\n",f[m]);
}
单调栈是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调栈经常被用于解决一些与数组相关的问题。 下面是单调栈的一般思路: 1. 创建一个空栈。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将当前元素入栈。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的栈中元素就是没有下一个更大或更小元素的元素。在使用单调栈求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减栈。具体操作如下: 1. 创建一个空栈和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入栈。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减栈求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值