【JZOJ2320】诡异游戏【dp】【堆】

15 篇文章 0 订阅

题目大意:

题目链接:https://jzoj.net/junior/#main/show/2320
在这里插入图片描述


思路:

被C组的题目血虐 q w q qwq qwq
在这里插入图片描述
感觉这道题还是不止C组难度的啊。拿到B组去应该也没几个人可以做出来。害得我一下午没有改A组题


把每一个花看成一个点。
首先由一个很显然的性质。如果我们已经取好了 [ 1 , x ] [1,x] [1,x]的所有花,那么我们下次一定会取一个连续区间 [ x + 1 , y ] [x+1,y] [x+1,y]的所有花。也就是说,不会存在某一个时刻我们取的花是不连续的。任意时刻取的花一定是一个左端点为1的连续区间。
所以有一个相对明显的 d p dp dp:设 f [ i ] f[i] f[i]表示取完 [ 1 , i ] [1,i] [1,i]所有的花并且走到点 i i i所需要的最小时间。
那么就可以枚举这一次取的花的区间 [ j , i ] [j,i] [j,i],那么我们需要求的就是从第 j j j朵花开始,在第 i i i朵花结束,并且取完 [ j , i ] [j,i] [j,i]所有花的最少花费时间。
那么如果我们第二次经过一朵花时,这朵花已经种下了,那么就不需要花费任何的时间等待。所以这样子所花费的时间如下图
在这里插入图片描述
其中红色线条表示采摘其中任意一朵花花费的时间。
我们发现,从第一次经过这个点,准备放下花,到我我们最后一次经过这个点,采摘这朵花,所需要的时间(红色线条)正好为 2 ( a [ i ] − a [ j ] ) 2(a[i]-a[j]) 2(a[i]a[j])
所以无论我们采摘奶一个花,我们花费的时间是与这朵花的位置无关的。
但是如果一朵花所需的等待时间 m m m是大于 2 ( a [ i ] − a [ j ] ) 2(a[i]-a[j]) 2(a[i]a[j])的话,那么我们就得等待 m − 2 ( a [ i ] − a [ j ] ) m-2(a[i]-a[j]) m2(a[i]a[j])秒。也就是说,采摘 [ l , r ] [l,r] [l,r]的花,我们需要花费时间为 m a x ( a [ r ] − a [ l ] , m ) max(a[r]-a[l],m) max(a[r]a[l],m)
所以这样我们就可以轻松的设计出一个 O ( n 2 ) O(n^2) O(n2) d p dp dp
f [ i ] = m i n ( f [ i ] , f [ j − 1 ] + a [ i ] − a [ j − 1 ] + m a x ( 2 × ( a [ i ] − a [ j ] ) , m ) ) f[i]=min(f[i],f[j-1]+a[i]-a[j-1]+max(2\times (a[i]-a[j]),m)) f[i]=min(f[i],f[j1]+a[i]a[j1]+max(2×(a[i]a[j]),m))
其中 a [ i ] − a [ j − 1 ] a[i]-a[j-1] a[i]a[j1]是从上一个结束的花 ( j − 1 ) (j-1) (j1) i i i的时间。
那么这样就可以获得 50 p t s 50pts 50pts的高分。
考虑如何优化这个 d p dp dp。我们发现这个 d p dp dp最关键的地方就是 m a x max max,如果我们知道谁大谁小就可以把这个 m a x max max省掉了。
也就是说
2 × ( a [ i ] − a [ j ] ) > m 2\times (a[i]-a[j])>m 2×(a[i]a[j])>m时,原方程为
f [ i ] = m i n ( f [ i ] , f [ j − 1 ] + a [ i ] − a [ j − 1 ] + 2 × ( a [ i ] − a [ j ] ) ) f[i]=min(f[i],f[j-1]+a[i]-a[j-1]+2\times (a[i]-a[j])) f[i]=min(f[i],f[j1]+a[i]a[j1]+2×(a[i]a[j]))
化简
f [ i ] = m i n ( f [ i ] , f [ j − 1 ] − a [ j − 1 ] − 2 a [ j ] + 3 a [ i ] ) f[i]=min(f[i],f[j-1]-a[j-1]-2a[j]+3a[i]) f[i]=min(f[i],f[j1]a[j1]2a[j]+3a[i])
同理,当 m > 2 × ( a [ i ] − a [ j ] ) m>2\times (a[i]-a[j]) m>2×(a[i]a[j]),原方程为
f [ i ] = m i n ( f [ i ] , f [ j − 1 ] + a [ i ] − a [ j − 1 ] + m ) f[i]=min(f[i],f[j-1]+a[i]-a[j-1]+m) f[i]=min(f[i],f[j1]+a[i]a[j1]+m)
我们发现,在枚举 i i i时, 3 a [ i ] 3a[i] 3a[i] a [ i ] a[i] a[i]都是固定的,所以我们就只需要分别维护 f [ j − 1 ] − a [ j − 1 ] − 2 a [ j ] f[j-1]-a[j-1]-2a[j] f[j1]a[j1]2a[j] f [ j − 1 ] + a [ i ] − a [ j − 1 ] f[j-1]+a[i]-a[j-1] f[j1]+a[i]a[j1]的最小值即可。
同时,容易发现在 i i i递增的过程中,满足 2 ( a [ i ] − a [ j ] ) &gt; m 2(a[i]-a[j])&gt;m 2(a[i]a[j])>m的花 j j j一定是一个区间 [ 1 , x ] [1,x] [1,x] x x x是单调不降的。所以我们就只要在把 i i i每往后挪一位时,利用单调不降的性质,维护好对应的有哪一些花是满足 2 ( a [ i ] − a [ j ] ) &gt; m 2(a[i]-a[j])&gt;m 2(a[i]a[j])>m,又有哪一些花是满足 2 ( a [ i ] − a [ j ] ) &lt; m 2(a[i]-a[j])&lt;m 2(a[i]a[j])<m的,然后分别用两个堆维护一下它们的最小值,然后取 m i n min min即可。
注意堆需要支持删除任意数字,那么就只能手写可删除堆了。
同时花的下标达到了 1 0 9 10^9 109级别,所以需要开两个 m a p map map,别分记录字典序和一个数出现的个数。这样复杂度又增加了 log ⁡ \log log
细节还是很多了,调了我一个下午+晚上。
时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)。比较卡,最好吸氧(狗头保命


代码:

#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const ll N=100010,Inf=1e18;
ll n,t,m,j,a[N],f[N];

struct Heap
{
    ll a[N],tot;
    map<ll,ll> way,cnt;
    //ll cnt[N];
    
    ll size()
    {
        return tot;
    }
    
    ll top()
    {
    	if (!tot) return Inf;
        return a[1];  
    }
    
    void push(ll x) 
    {
        cnt[x]++;
        if (cnt[x]>1) return;
        a[++tot]=x;
        ll fa=(tot>>1),k=tot;
        way[x]=way[a[fa]]*2+(tot&1);
        while (a[fa]>a[k]&&fa)
        {
            swap(way[a[k]],way[a[fa]]);
            swap(a[k],a[fa]);
            k=fa; fa>>=1; 
        }
    }
    
    void pop(ll x)
    {
        cnt[a[x]]--;
        if (cnt[a[x]]) return;
        swap(way[a[x]],way[a[tot]]);
        swap(a[x],a[tot]); 
        way[a[tot]]=0;
        a[tot]=0;
        tot--;      
        ll son=(x<<1);
        while ((a[son]<a[x]&&son<=tot)||(a[son+1]<a[x]&&son+1<=tot))      
        {
            if (a[son+1]<a[son]&&son+1<=tot) son++;
            swap(way[a[x]],way[a[son]]);
            swap(a[x],a[son]);
            x=son; son<<=1; 
        }
    }
}q1,q2; 

int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%lld%lld%lld",&n,&t,&m); n++;
	for (ll i=2;i<=n;i++)
		scanf("%lld",&a[i]);
	f[1]=0; j=2;
	for (ll i=2;i<=n;i++)
	{
		q2.push(f[i-1]-a[i-1]+m);
		for (j<=i;2LL*(a[i]-a[j])>m;j++)
		{
			q2.pop(q2.way[f[j-1]-a[j-1]+m]);
			q1.push(f[j-1]-a[j-1]-2*a[j]);
		}
		f[i]=min(q1.top()+3*a[i],q2.top()+a[i]);
		//if (i==30) printf("%lld\n",f[i]);
	}
	printf("%lld",f[n]+t-a[n]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值