1. 程式人生 > >bzoj 1044: [HAOI2008]木棍分割 二分答案+動態規劃

bzoj 1044: [HAOI2008]木棍分割 二分答案+動態規劃

題意:有n根木棍, 第i根木棍的長度為Li,n根木棍依次連結了一起, 總共有n-1個連線處. 現在允許你最多砍斷m個連
接處, 砍完後n根木棍被分成了很多段,要求滿足總長度最大的一段長度最小, 並且輸出有多少種砍的方法使得總長

度最大的一段長度最小. 並將結果mod 10007。

分析:第一問二分答案然後貪心。

第二問f[i,j]表示前j根木棍中切i刀有多少種方案。

f[i,j]=sum(f[i-1,l-1]){sum[j]-sum[l-1]<=ans}

sum[i]表示木棍的字首和。

我們可以維護一個字首和,那麼只要知道l就可以O(1)轉移。很容易想到l是單調遞增的,那麼亂搞一下就好了。

還有陣列要開滾動的。

一開始我WA的原因是取模後進行了減法沒有判斷為負數的情況。

程式碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define MOD 10007
#define N 50005
using namespace std;

int f[N],sum[N],a[N],p[N];

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	int l=1,r=sum[n],ans;
	while (l<=r)
	{
		int mid=(l+r)/2,len=0,s=0,flag=0;
		for (int i=1;i<=n;i++)
		{
			if (a[i]>mid) 
			{
				flag=1;
				break;
			}
			if (len+a[i]>mid)
			{
				s++;
				len=a[i];
			}
			else len+=a[i];
			if (s>m)
			{
				flag=1;
				break;
			}
		}
		if (!flag)
		{
			ans=mid;
			r=mid-1;
		}
		else
		l=mid+1;
	}
	for (int i=1;i<=n;i++)
		if (sum[i]<=ans) f[i]=1;
		else break;
	int tot=f[n];
	for (int i=1;i<=m;i++)
	{
		for (int j=1;j<=n;j++)
			p[j]=(p[j-1]+f[j])%MOD;
		int l=1;
		memset(f,0,sizeof(f));
		for (int j=i+1;j<=n;j++)
		{
			while (sum[j]-sum[l-1]>ans) l++;
			f[j]=(p[j-1]-p[l-2]+MOD)%MOD;
		}
		tot=(tot+f[n])%MOD;
	}
	printf("%d %d",ans,tot);
	return 0;
}