题目描述
题目链接
n个数分成m堆,每堆的贡献是(最大值-最小值)^2,求最小贡献,n<=10000,m<=1000
题目分析
先从小到大排序,然后妥妥的O(nm)斜率优化dp
但是我(蒟蒻)不小心想到了WQS二分,然后满怀激动地觉得可以做到O(nlogm)
于是:
怎么会WA??!!
想一想,选的堆数越多,答案肯定越小,按道理讲函数图像应该是个下凸包,只需要二分出每选一堆会加上的值k,然后斜率优化O(n)求出不考虑堆数下的最小值,再根据到达最小值取的堆数调整k值,使得取到最小值的堆数恰为m堆,那么选m堆的原答案肯定就是求出的最小值减去m*k啊,毫无问题!
然后我开始了对拍。。。
啪!
一下拍中。。。
4 3
1 2 8 9
答案应该是1,但是我出了0
研究一下,发现k=0的时候选4堆最优(=0),k=2的时候选2堆最优(=6),k=1的时候选2,3,4堆的值是一样的(=4)!
于是我的二分出的答案最后选了4堆,变成了4-1*4=0
所以WQS二分在几个点相等的时候,你的程序会选择多一堆还是少一堆决定了你的二分的l,r应该哪个+1或-1,最后的答案必须是-m*k,不能是-(最后一次二分选的堆数)*k
想清楚之后:
1600ms
→
\rarr
→ 30ms! tql!(膜拜WQS二分(听说也叫带权二分))
把三次代码都贴出来吧:
这个是O(nm)的斜率优化:
#include<cstdio>
#include<algorithm>
#define maxn 10005
using namespace std;
int T,n,m,cnt[maxn],q[maxn],head,tail,now;
int f[2][maxn],a[maxn];
inline int gety(int i,int j){return f[now][i]+a[i+1]*a[i+1]-f[now][j]-a[j+1]*a[j+1];}
inline int getx(int i,int j){return 2*(a[i+1]-a[j+1]);}
int main()
{
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
now=1;
for(int i=1;i<=n;i++) f[0][i]=(a[i]-a[1])*(a[i]-a[1]);
for(int k=2;k<=m;k++)
{
now=k&1;
q[head=0]=0,tail=1;
for(int i=1;i<=n;i++)
{
while(head+1<tail&&gety(q[head+1],q[head])<=a[i]*getx(q[head+1],q[head])) head++;
f[now^1][i]=f[now][q[head]]+(a[i]-a[q[head]+1])*(a[i]-a[q[head]+1]);
cnt[i]=cnt[q[head]]+1;
while(head+1<tail&&gety(q[tail-1],q[tail-2])*getx(i,q[tail-1])>=getx(q[tail-1],q[tail-2])*gety(i,q[tail-1])) tail--;
q[tail++]=i;
}
}
printf("Case %d: %d\n",t,f[now^1][n]);
}
}
这个是有问题的WQS二分:
#include<cstdio>
#include<algorithm>
#define maxn 10005
using namespace std;
int T,n,m,cnt[maxn],q[maxn],head,tail;
long long f[maxn],a[maxn];
inline long long gety(int i,int j){return f[i]+a[i+1]*a[i+1]-f[j]-a[j+1]*a[j+1];}
inline long long getx(int i,int j){return 2*(a[i+1]-a[j+1]);}
inline int check(int P)
{
q[head=0]=0,tail=1;
for(int i=1;i<=n;i++)
{
while(head+1<tail&&gety(q[head+1],q[head])<=a[i]*getx(q[head+1],q[head])) head++;
f[i]=f[q[head]]+(a[i]-a[q[head]+1])*(a[i]-a[q[head]+1])+P;
cnt[i]=cnt[q[head]]+1;
while(head+1<tail&&gety(q[tail-1],q[tail-2])*getx(i,q[tail-1])>=getx(q[tail-1],q[tail-2])*gety(i,q[tail-1])) tail--;
q[tail++]=i;
}
f[n]-=cnt[n]*P;//这里有问题
return cnt[n];
}
int main()
{
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
sort(a+1,a+1+n);
int l=0,r=(a[n]-a[1])*(a[n]-a[1]),mid,tmp;//这个r应该开long long
while(l<r)
{
mid=(l+r)>>1;
tmp=check(mid);
if(tmp==m) break;//这个其实没必要
else if(tmp>m) l=mid+1;//我的dp是相等的时候优先取堆数大的,所以l不能+1
else r=mid-1;
}
printf("Case %d: %lld\n",t,f[n]);
}
}
这个是AC的WQS二分:
#include<cstdio>
#include<algorithm>
#define maxn 10005
using namespace std;
int T,n,m,cnt[maxn],q[maxn],head,tail;
long long f[maxn],a[maxn];
inline long long gety(int i,int j){return f[i]+a[i+1]*a[i+1]-f[j]-a[j+1]*a[j+1];}
inline long long getx(int i,int j){return 2*(a[i+1]-a[j+1]);}
inline int check(long long P)
{
q[head=0]=0,tail=1;
for(int i=1;i<=n;i++)
{
while(head+1<tail&&gety(q[head+1],q[head])<=a[i]*getx(q[head+1],q[head])) head++;
f[i]=f[q[head]]+(a[i]-a[q[head]+1])*(a[i]-a[q[head]+1])+P;
cnt[i]=cnt[q[head]]+1;
while(head+1<tail&&gety(q[tail-1],q[tail-2])*getx(i,q[tail-1])>=getx(q[tail-1],q[tail-2])*gety(i,q[tail-1])) tail--;
q[tail++]=i;
}
return cnt[n];
}
int main()
{
scanf("%d",&T);
for(int t=1;t<=T;t++)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
sort(a+1,a+1+n);
long long l=0,r=(a[n]-a[1])*(a[n]-a[1]),mid;
while(l<r)
{
mid=(l+r+1)/2;
if(check(mid)>=m) l=mid;
else r=mid-1;
}
check(l);//最后出来再计算一次
printf("Case %d: %lld\n",t,f[n]-l*m);
}
}