BZOJ1044 [HAOI2008]木棍分割(二分答案/单调性优化dp+递推优化)

我要吐槽,因为我被这题坑了一个晚上

看网上的博客都写了二分答案的解法,可偏偏我就用了斜率优化dp的分析思路
“设f[i][j]:前i个数分j段的最小值 ……”

竟然还分析出来了。。。

无奈各种诸如<,<=这样的边界情况巨坑,导致我推了一页纸+调了一晚上

结果第一问的f[n][m&1]求对了,然而第二问的cnt[n][m&1]并不能在f[i][j]转移到f[n][m&1]时累加cnt[i][j]

为什么:3145切两刀,求f[3][2],显然这唯一一刀切在31和4中间最优,这个子问题答案是4

              可以通过f[3][2]推得f[4][3]=5,然而cnt[4][3]并不是"+=cnt[3][2]",因为答案是5,前三个数分两段,刀也可以切在3和14中间,这被漏掉了。。。

所以总共写了两个dp,在COGS上过掉后,放到BZOJ,竟然TLE了

这一晚上真是逗QAQ

实在懒得改了,把第一问单调性优化dp的摆上来吧


另:纪念博客100篇 QAQ


【题解】

f[i][j]:前i个数分j段的最小值 
设 f[i][x]:前i个数分j段的最小值 
f[i][x]=min{ max(f[j][x-1],s[i]-s[j]) }
二分答案即可 
然而我的方法类似于斜率优化:
假设j比k优,讨论j,k的大小关系,可得(只写最后结论):
1) j<k(前优) f[j][x-1]<f[k][x-1] 且 s[j]+f[k][x-1]>s[i] 
2) k<j(后优) s[k]+f[j][x-1]
可以根据这个,用单调队列维护一个 j<k时s[j]+f[k][x-1]递增的东西,O(m*n)得出f数组 

设 cnt[i][x]:第一问答案ans1确定后,前i个数分j段,每段长不超过ans1的方案数 
cnt[i][x]=sigma( cnt[j][x-1] ),其中 s[i]-s[j]<=ans1; j<i

注意到对于同一个x,随着i的增大,j的可行区间左端点是不断右移的,而右端点为i-1,因此用k记录左端点,用sum记录区间和,即可做到O(m*n)的递推 


【代码】

#include<stdio.h>
#include<stdlib.h>
#define MOD 10007
int s[50005],f[50005][2],cnt[50005][2],q[50005];
int max(int a,int b)
{
	if(a>b) return a;
	return b;
}
int main()
{
	int n,m,i,j,k,head,tail,ans,sum;
	scanf("%d%d",&n,&m);
	m++;
	for(i=1;i<=n;i++)
	{
		scanf("%d",&s[i]);
		s[i]+=s[i-1];
	}
	for(i=1;i<=n;i++)
		f[i][1]=s[i];
	for(j=2;j<=m;j++)//f[i][j]:前i个数分j段的最小值 
	{
		head=0;
		tail=1;
		q[0]=j-1;
		for(i=j;i<=n;i++)
		{
			while( tail-head>1 && s[q[head]]+f[q[head+1]][j-1&1] < s[i] ) head++;
			f[i][j&1]=max(f[q[head]][j-1&1],s[i]-s[q[head]]);
			while( tail-head>1 && f[q[tail-2]][j-1&1]<f[q[tail-1]][j-1&1] && s[q[tail-2]]+f[q[tail-1]][j-1&1] > s[q[tail-1]]+f[i][j-1&1] ) tail--;
			q[tail++]=i;
		}
	}
	ans=f[n][m&1];
	cnt[0][0]=cnt[0][1]=1;
	for(j=1;j<=m;j++)
	{
		k=sum=0;
		for(i=1;i<=n;i++)
		{
			for(;s[i]-s[k]>ans;k++)
				sum-=cnt[k][j-1&1];
			sum+=cnt[i-1][j-1&1];
			cnt[i][j&1]=sum%MOD;//别忘取模!
		}
	}
	printf("%d %d",ans,cnt[n][m&1]);
	return 0;
}

你可能感兴趣的:(dp,递推,二分答案,单调性优化)