题解 - [HAOI2008]木棍分割

[ H A O I 2008 ] \mathrm{[HAOI2008]} 木棍分割

吐槽环节

  • 为什么一分部分分不给啊,一开始写了个 n 3 n^3 ,居然直接爆蛋。
  • 前缀和优化 D P DP 还要滚存

题目意思

S o l \mathrm{Sol}

  • 前置知识:前缀和优化DP
  • 对于第一问就是个 h h hh 问题,直接二分就可以了。这个没什么好讲,看看代码:
inline bool check(int mid) 
{
	int sum=0,all=0;
	//mid:二分的答案,sum:分的组数,all:当前组的总长
	for ( int i=1;i<=n;i++ ) 
	{
		if(a[i]>mid) return false;//有一段大于mid显然不合理
		if(all+a[i]>mid) 
			all=a[i],sum++;
		else all+=a[i];
	}
	return (sum<=m);//判断需要分的最小组数<=m
}
  • 对于第二问我们可以用 D P DP 来解决。首先我们会显而易见的想到 f i , j f_{i,j} 表示到第 i i 根木棒,分成 j j 组的方案数。然后就是直接 n 3 n^3 做: f i , j = k = 1 i 1 f k , j 1 [ s u m ( i ) s u m ( k ) x ] f_{i,j}=\sum_{k=1}^{i-1} f_{k,j-1}[sum(i)-sum(k)\leq x] 。我们再来看看 n 3 n^3 的代码(不要以为你会有分,就是爆蛋)
inline int n_three_dp(int x)//函数名就是n^3,多好
{
	for ( int i=1;i<=n;i++ ) 
		if(sum[i]<=x) f[i][0]=1;//初始化(不用分能将连续几根分在一起)
	for ( int j=1;j<=m;j++ ) //枚举组数
		for ( int i=1;i<=n;i++ )
			for ( int k=0;k<i;k++ ) 
				if(sum[i]-sum[k]<=x) //dp转移
					Add(f[i][j],f[k][j-1]); 
	int ans=0;
	for ( int i=0;i<=m;i++ ) Add(ans,f[n][i]);//统计答案
	return ans;
}
  • 到现在我们还是 0 0 分,我们考虑用前缀和优化。 p r e i , j = k = 1 j f i , k pre_{i,j}=\sum_{k=1}^{j}f_{i,k} ,然后我们用前缀和去更新 f i , j f_{i,j} ,就是 f i , j = p r e i 1 , j p r e i 1 , k 1 f_{i,j}=pre_{i-1,j}-pre_{i-1,k-1} 。然后我们再交一下,发现还是 0 0 分(qwq…
  • 那我们仔细观察这个转移发现对于一个固定的 j j 我们去枚举若干个 k k 会浪费时间。于是我们记录第一个 k k 满足 s u m i s u m k x sum_i-sum_k\leq x 的位置为 P i P_i 。这里有点像容斥(就是总方案数减去不合法方案数)。
  • 对于每个 P i P_i 我们考虑到 s u m sum 的单调性,不需要 O ( n 2 ) O(n^2) 计算。看看代码
for ( int i=1;i<=n;i++ ) 
		for ( ;j<i;j++ ) //利用sum的单调性
			if(sum[i]-sum[j]<=x) 
			{
				ed[i]=j;
				break;
			}
  • 这样我们时空都是 O ( n × m ) O(n\times m) ,空间不行,考虑滚存(后面就是简单套路了, f [ ] , p r e [ ] f[],pre[] 互相更新即可。
  • 具体看代码,这样我们就成功把空间降到 O ( n ) O(n) ,能过

C o d e \mathrm{Code}

#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")

#include <bits/stdc++.h>
#define pb push_back
//#define int long long
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=5e4+5;
const int mo=10007;

int n,m,ans,a[N],sum[N],f[105][1005],g[N],pre[N],ed[N],ret;

inline bool check(int mid,int &s) 
{
	int sum=0,all=0;
	for ( int i=1;i<=n;i++ ) 
	{
		if(a[i]>mid) return false;
		if(all+a[i]>mid) 
			all=a[i],sum++;
		else all+=a[i];
	}
	s=sum+(all!=0);
	return (sum<=m);
}

inline void Add(int &x,int y)
{
	x+=y;
	if(x>=mo) x%=mo;
}

inline int n_three_dp(int x)
{
	for ( int i=1;i<=n;i++ ) 
		if(sum[i]<=x) f[i][0]=1;
	for ( int j=1;j<=m;j++ ) 
		for ( int i=1;i<=n;i++ )
			for ( int k=0;k<i;k++ ) 
				if(sum[i]-sum[k]<=x) 
					Add(f[i][j],f[k][j-1]); 
	int ans=0;
	for ( int i=0;i<=m;i++ ) Add(ans,f[n][i]);
	return ans;
}

inline int dp(int x)
{
	for ( int i=1;i<=n;i++ ) 
	{
		if(sum[i]<=x) g[i]=1;
		pre[i]=pre[i-1]+g[i];
	}
	int j=0;
	for ( int i=1;i<=n;i++ ) 
		for ( ;j<i;j++ ) if(sum[i]-sum[j]<=x) 
			{
				ed[i]=j;
				break;
			}
	for ( int j=2;j<=m+1;j++ ) 
	{
		for ( int i=1;i<=n;i++ ) 
		{
			g[i]=pre[i-1];
			if(ed[i]>=1) g[i]=(g[i]-pre[ed[i]-1]+mo)%mo;
		}
		pre[0]=0;
		for ( int i=1;i<=n;i++ ) 
			pre[i]=(pre[i-1]+g[i])%mo;
		Add(ret,g[n]);
	}
	ret+=(sum[n]<=x);
	return (ret+mo)%mo;
}
			
signed main()
{
	n=read();
	m=read();
	int mx=0;
	for ( int i=1;i<=n;i++ ) 
	{
		a[i]=read();
		mx=max(mx,a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	int l=mx,r=sum[n];
	ret=0;
	while(l<=r)
	{
		int mid=(l+r)/2;
		int s=0;
		if(check(mid,s)) 
			ans=mid,r=mid-1;
		else l=mid+1;
	}
	if(n<=100) 
		printf("%d %d\n",ans,n_three_dp(ans));
	else 
		printf("%d %d\n",ans,dp(ans));
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wangyiyang2/article/details/105871975
今日推荐