[JZOJ5427]吃草

问题描述

后院总共有n片草坪,第i片草坪投影到数轴上,是一段l[i]到r[i]的闭区间,保证l[i]+r[i]是偶数,l[i]<=r[i]。
Sullivan可以在整点上放0v0来把草吃掉(于是0v0变成了0π0)。如果第i片草坪覆盖了x点上的0π0(l[i]<=x<=r[i]),那么这只0π0就可以吃掉这片草坪里的草。每一片草坪的草需要且只能被一只0π0吃掉。如果一片草坪覆盖了多只0π0,Sullivan可以选择任意一只去吃草。
但是,0π0吃草是有代价的,对于第i片草坪,假如吃草的0π0位于x点上,代价为abs((x-l[i])-(r[i]-x)),即0π0到草坪两端距离之差。
现在,Sullivan想知道:
1.最少需要放几只0v0?
2.在放最少只数的0v0情况下,代价最小是多少?

子任务

20% n,l[i],r[i]<=3000 t=0
30% n,l[i],r[i]<=300000 t=0
20% n,l[i],r[i]<=3000 t=1
30% n,l[i],r[i]<=300000 t=1

输出

第一行一个非负整数,为最小的0v0数量。
如果t=0,没有第二行输出;如果t=1,第二行输出在放最少只数的0v0情况的最小代价。

分析

如果只有第一问就是经典的贪心了嘛。
现在考虑怎么做第二问。

暴力

我们考虑dp,如果用做到第几个区间设状态,感觉不太可做,发现位置很小,尝试位置。设状态i表示最后一个牛(0v0)放在位置i。f[i]表示用的最少牛数,g[i]表示花费数。
先考虑如何求f[i],这次我们用DP,实际上他跟贪心是差不多的。考虑i由j转移来,如果我们两个牛隔太远,有可能会让有的区间上没有牛,那么先考虑合法的j。条件就是[j+1,i-1]之间没有完整的区间,这个可以用数组预处理求出。我们又要f[i]最小,那么j应该是尽量前的点,因为越往后f肯定越大。从贪心之类的我们可以感性感觉出,f数组大概是0,0,0,1,1,1,1,2,2,2,2,3,3,4,4这种一种值分布在一块,然后依次递增的。那么使得f最优的j一定是合法的那些j的前一段。
考虑g[i]怎么暴力转移,在满足f[i]最小的j里面,我们找费用最小的转移,g[i]=g[j]+cost(j,i)。考虑cost怎么计算。我们知道对于某一个区间,牛放在中点费用最小,为0,否则就是中点到牛的距离*2。那么我们把区间的中点mid记录下来,再设 m=(j+i)/2 ,那么对于 j<=mid<=m 的区间我们让他属于左边的牛, m<mid<=i 的属于右边,这一定是最优的,然后可以用前缀和数组O(1)算出。注意第一个和最后一个牛放的时候要加上没有统计的区间,即中点不被任何两头牛夹的区间。
那么n^2暴力就写出来了,这个其实也挺难卡的,比赛不够时间可以写这个。

决策单调性证明

一般dp题搞到这种程度就只能决策优化一下吧…设s[i]表示状态i的决策,我们会有s[i-1]<=s[i]。怎么证明呢?设 j<j ,在i不断变大的时候,他们的中点也在不断变大。对于某一个中点在i的区间,一开始它肯定选择走到i,使得费用最小,随着中点不断向右移,费用会变大,但是当中点跨过他的时候,他就会选择j(j’)了,这时候费用就不变了;而j和i的中点比j’和i的中点更右,区间们进入费用不变的状态会更早。换句话说,cost(j,i)的增量比cost(j’,i)小,那么他们的费用图像(横轴为i,纵轴为cost(j,i)+g[j])只有一个交点,这就有决策单调性了。

决策单调性优化

我们套用经典方法,使用单调队列维护答案。
队头维护了转移到i的花费最小的合法的j,每次i++的时候先判队头合法性,在合法的情况下,如果队头比第二个劣它就弹出去,为了维护合法性,如果 f[]<f[] ,是不能弹的。
做完i我们把它扔进队尾,设当前队尾为y,y前一个为x。什么情况下y没有用呢?一般做法是y比x优的位置大于等于i比y优的地方,就要弹出。为了找位置,我们使用一个二分,这和斜率优化就差了一点东西。
剩下有一些细节,是考试的时候很容易错的地方,要注意,如果想细想这道题最好不要看了。

在二分的时候,如果二分到的位置是编号较小的无法转移到的,那么就判断这个位置是编号较大的更优,不管编号较大的能否转移到。这里为了维护合法性,还得注意到只有f[x]=f[y]=f[i]的时候才能弹。
我打的时候,受到了凸包的影响,求了i和x的交点…搞了半天。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
#include<map>
using namespace std;
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=6e5+5; 
struct rec
{
    int x,y;
}a[N];
int mx,n,s,i,j,p,ans1,f[N],mn,m,k,S[N],rig,tpx[N],l,r,dl[N],k3,k4,k5,y,z;
ll tot[N],cnt[N],ans2,g[N],k1,k2;
ll calc(int x,int y)
{
    m=(x+y)/2;
    return (tot[m]-tot[x]-x*(cnt[m]-cnt[x])+y*(cnt[y]-cnt[m])-(tot[y]-tot[m]))*2;
}
int cross(int x,int y)
{
    int l=max(x,y),r=mx+1,m;
    while (l<r)
    {
        m=(l+r)/2;
        if (tpx[m]>x||g[x]+calc(x,m)>=g[y]+calc(y,m)) r=m;
        else l=m+1;
    }
    return l;
    //在l,y成为最优点。 
}
void ins(int x)
{
    while (l<r&&f[dl[r]]==f[dl[r-1]]&&f[x]==f[dl[r]]&&cross(dl[r-1],(y=dl[r]))>cross(dl[r],x))
        dl[r--]=0;
    dl[++r]=x;
}
int main()
{
    freopen("t3.in","r",stdin);
    //freopen("t3cor.out","w",stdout);
    scanf("%d %d",&n,&s);
    mn=1e9;
    fo(i,1,n) 
    {
        scanf("%d %d",&a[i].x,&a[i].y);
        m=(a[i].x+a[i].y)/2;
        cnt[m]++;
        tot[m]+=m;
        cmax(rig,a[i].x);
        cmax(mx,a[i].y);
        cmin(mn,a[i].y);
        cmax(tpx[a[i].y+1],a[i].x);
    }
    fo(i,1,mx+1) cnt[i]+=cnt[i-1],tot[i]+=tot[i-1];
    fo(i,1,n) cmax(tpx[i],tpx[i-1]);
    fo(i,1,mx) if (cnt[i]) break;
    fo(i,i,mn) 
    {
        f[i]=1;
        g[i]=(cnt[i]*i-tot[i])*2;
        ins(i);
    }
    fo(i,i,mx)
    {
        while (dl[l]<tpx[i]) dl[l++]=0;
        while (l<r&&f[dl[l]]==f[dl[l+1]]&&g[dl[l]]+calc(dl[l],i)>=g[dl[l+1]]+calc(dl[l+1],i)) 
            dl[l++]=0;
        S[i]=dl[l];
        f[i]=f[S[i]]+1;
        g[i]=g[S[i]]+calc(S[i],i);
        ins(i);
    }
    ans1=1e9;
    fo(i,rig,mx)
    {
        if (f[i]<ans1)
        {
            ans1=f[i];
            ans2=g[i]+(tot[mx]-tot[i]-i*(cnt[mx]-cnt[i]))*2;
        }else if (f[i]==ans1)
            cmin(ans2,g[i]+(tot[mx]-tot[i]-i*(cnt[mx]-cnt[i]))*2);
    }
    printf("%d\n",ans1);
    if (s) printf("%d",ans2);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值