问题描述
后院总共有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);
}