[JZOJ5427]吃草

来源:互联网 发布:守望先锋天使数据 编辑:程序博客网 时间:2024/04/30 14:45

问题描述

后院总共有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);}
原创粉丝点击