• 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;
    }
    
  • 相关阅读:
    心慌慌
    辛苦了
    [转]家庭长寿秘方:夫妻关系之守则。
    无题
    浮躁
    [转]樱木花道9大缺点
    一票难求
    Excel录入数据,自动记录当前时间点
    Excel数据透视表基本使用
    linux下tomcat7虚拟目录配置
  • 原文地址:https://www.cnblogs.com/Akmaey/p/15264367.html
Copyright © 2020-2023  润新知