传送门:BZOJ1044
第一问:傻逼二分,设答案为ans,二分贪心即可。
第二问:
f(i,j)
表示第i刀切在第j处的合法方案数
转移是
f(i,j)=∑len[k,j]≤ansj−1f(i−1,k)
显然这个Dp空间是 O(nm) 的,时间是 O(nm2) 的,无法承受。
首先,我们可以预处理对于每个j,k的最小值,因为j,k都是不严格单调的,这一步可以在线性的时间内完成
其次,在求f(n)时,我们可以求f(n−1)数组的前缀和g,这一步是O(m)的,然后用上面预处理出的k在O(1)时间内完成转移。
至于空间,显然可以滚动。
于是本题的时间复杂度就变成了O(n+nlogn+nm),空间变成了O(m) 。
AC之后的吐槽。
你个xxxxx,把傻逼二分写错了调了半天是吧!
g数组不能取模,即使取模也只能用经典的减法意义下的取模!
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int p=10007;
int n,m;
int da[50005];
int sum[50005];
int pre[50005];
int ans,num;
int f[3][50005];
int g[50005];
bool can(int ans)
{
int num=0,jec=0;
for(int i=1;i<=n;i++){
if(num+da[i]<=ans)
num+=da[i];
else
if(da[i]>ans)
return false;
else
jec++,num=da[i];
}
return jec<=m;
}
int Middle()
{
int a=1,b=100000005;
while(a<b){
int mid=(a+b)/2;
if(can(mid))
b=mid;
else
a=mid+1;
}
return a;
}
void First()
{
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+da[i];
ans=Middle();
int j=1;
for(int i=1;i<=n;i++){
while(sum[i]-sum[j]>ans)
j++;
pre[i]=j-1;
}
}
void Solve()
{
for(int i=1;i<=n;i++)
if(sum[i]<=ans)
f[1][i]=1;
else
break;
int o=0;
for(int i=n-1;i>=1;i--)
if(sum[n]-sum[i]>ans)
break;
else{
num+=f[o^1][i];
num%=p;
}
for(int i=2;i<=m;i++){
for(int j=1;j<=n;j++){
g[j]=g[j-1]+f[o^1][j];
//g[j]%=p;
}
for(int j=1;j<=n;j++){
f[o][j]=g[j-1]-g[pre[j]];
f[o][j]%=p;
}
for(int j=n-1;j>=1;j--)
if(sum[n]-sum[j]>ans)
break;
else{
num+=f[o][j];
num%=p;
}
o^=1;
}
/*for(int i=1;i<=n;i++)
printf("%d\n",f[o^1][i]);*/
printf("%d %d",ans,num);
}
void Readdata()
{
freopen("loli.in","r",stdin);
//freopen("loli.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&da[i]);
}
void Close()
{
fclose(stdin);
fclose(stdout);
}
int main()
{
Readdata();
First();
Solve();
Close();
return 0;
}