【JZOJ2320】诡异游戏【dp】【堆】
题目大意:
题目链接:https://jzoj.net/junior/#main/show/2320
思路:
被C组的题目血虐
感觉这道题还是不止C组难度的啊。拿到B组去应该也没几个人可以做出来。害得我一下午没有改A组题
把每一个花看成一个点。
首先由一个很显然的性质。如果我们已经取好了的所有花,那么我们下次一定会取一个连续区间的所有花。也就是说,不会存在某一个时刻我们取的花是不连续的。任意时刻取的花一定是一个左端点为1的连续区间。
所以有一个相对明显的:设表示取完所有的花并且走到点所需要的最小时间。
那么就可以枚举这一次取的花的区间,那么我们需要求的就是从第朵花开始,在第朵花结束,并且取完所有花的最少花费时间。
那么如果我们第二次经过一朵花时,这朵花已经种下了,那么就不需要花费任何的时间等待。所以这样子所花费的时间如下图
其中红色线条表示采摘其中任意一朵花花费的时间。
我们发现,从第一次经过这个点,准备放下花,到我我们最后一次经过这个点,采摘这朵花,所需要的时间(红色线条)正好为!
所以无论我们采摘奶一个花,我们花费的时间是与这朵花的位置无关的。
但是如果一朵花所需的等待时间是大于的话,那么我们就得等待秒。也就是说,采摘的花,我们需要花费时间为。
所以这样我们就可以轻松的设计出一个的。
其中是从上一个结束的花到的时间。
那么这样就可以获得的高分。
考虑如何优化这个。我们发现这个最关键的地方就是,如果我们知道谁大谁小就可以把这个省掉了。
也就是说
当时,原方程为
化简
同理,当,原方程为
我们发现,在枚举时,和都是固定的,所以我们就只需要分别维护和的最小值即可。
同时,容易发现在递增的过程中,满足的花一定是一个区间且是单调不降的。所以我们就只要在把每往后挪一位时,利用单调不降的性质,维护好对应的有哪一些花是满足,又有哪一些花是满足的,然后分别用两个堆维护一下它们的最小值,然后取即可。
注意堆需要支持删除任意数字,那么就只能手写可删除堆了。
同时花的下标达到了级别,所以需要开两个,别分记录字典序和一个数出现的个数。这样复杂度又增加了。
细节还是很多了,调了我一个下午+晚上。
时间复杂度。比较卡,最好吸氧(狗头保命
代码:
#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;
}