HDU 3480 Division 【斜率优化/WQS二分】

题目描述

题目链接
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

想清楚之后:
HDU 3480 Division 【斜率优化/WQS二分】_第1张图片
1600ms → \rarr 30ms! tql!(膜拜WQS二分(听说也叫带权二分))
把三次代码都贴出来吧:
这个是O(nm)的斜率优化:

#include
#include
#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
#include
#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
#include
#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);
	}
}

你可能感兴趣的:(DP,分治(二分))