305 . [HAOI2008] 木棍分割
★★★☆ 输入文件:stick.in 输出文件:stick.out 简单对比
时间限制:3 s 内存限制:64 MB
【问题描述】
有n根木棍,第i根木棍的长度为Li,n根木棍依次连结在一起,总共有n-1个连接处.现在允许你最多砍断m个连接处,砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小,并且输出有多少种砍木棍的方法使得总长度最大的一段长度最小.
【输入格式】
输入文件第一行有2个数n,m
接下来n行每行一个正整数Li,表示第i根木棍的长度.
【输出格式】
输出有2个数,第一个数是总长度最大的一段的长度最小值,第二个数是有多少种砍的方法使得满足条件.
对答案mod10007.
【输入样例】
3 2
1
1
10
【输出样例】
10 2
样例说明:两种砍的方法:(1)(1)(10)和(11)(10)
【数据范围】
n<=50000,0<=m<=min(n-1,1000)
1<=Li<=1000
我一开始写了两遍O(mn)的DP,结果跑了8s多,一看别人的,第一遍都是先二分出来的,时间复杂度
O(mn+nlog2(∑1≤i≤nli?max1≤i≤n{li}))
,加上各种技巧,1s内就可以跑出来.
①这个题给我最大的教训就是要二分的重要性!如果发现了任何单调性,首先一定要想到二分!
二分的话,即使不贪心,也是有O(n)的DP的.
②然后说一下贪心的正确性,假如说最大连续子区间<=mid时存在一种合法方案,那么显然只需要我们可以从最左边开始向右贪心,选择连续区间,如果当前的数可以加到上一个区间里,那么就加,否则就为它新开一个区间,这个思路其实跟活动选择是类似的,因为这样的话我们可以为以后的选择留尽量少的数,而令之后的选择至少不更劣.从而保证了正确性。但是遗憾的是,显然如果有负数的话,这是错误的,因为可能留给后面更多的数反而不更劣.
③如果多次DP其转移的形式都是一样的,而其转移求起来却比较复杂,那么我们大可以一遍预处理出来啊!
Code(纯DP):
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define P 10007
#include<algorithm>
inline void in(int &x){
char c=getchar();
x=0;
while(c<‘0‘||c>‘9‘)c=getchar();
for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=x*10+c-‘0‘;
}
int f[2][50005],s[50005],fs[50005];
int main(){
freopen("stick.in","r",stdin);
freopen("stick.out","w",stdout);
int n,m;
in(n),in(m);
int i,j,L;
for(i=1;i<=n;++i){
in(L);
s[i]=s[i-1]+L;
}
for(i=1;i<=n;++i)f[0][i]=s[i];
int p,now;
f[1][1]=s[1];
for(i=1;i<=n;++i)f[0][i]=s[i];
for(j=1;j<=m;++j){
now=j&1,p=1;
for(i=2;i<=n;++i){
while(p+1<i&&f[!now][p+1]<s[i]-s[p+1])++p;
if(p+1<i)f[now][i]=min(max(f[!now][p],s[i]-s[p]),max(f[!now][p+1],s[i]-s[p+1]));
else f[now][i]=max(f[!now][p],s[i]-s[p]);
}
}
L=f[m&1][n];
memset(f,0,sizeof(f));
int ans=0;
for(i=1;i<=n&&s[i]<=L;++i)f[0][i]=1;
for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[0][i])%P;
ans=f[0][n];
for(j=1;j<=m;++j){
//printf("-----%d-----\n",j);
p=1,now=j&1,f[now][1]=0;
for(i=2;i<=n;++i){
while(s[i]-s[p]>L)++p;
f[now][i]=(fs[i-1]-fs[p-1])%P;
}
memset(fs,0,sizeof(fs));
for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[now][i])%P;
ans=(ans+f[now][n])%P;
}
printf("%d %d",L,(ans+P)%P);
}
Code(二分+贪心+DP):
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define P 10007
#include<algorithm>
inline void in(int &x){
char c=getchar();
x=0;
while(c<‘0‘||c>‘9‘)c=getchar();
for(;c>=‘0‘&&c<=‘9‘;c=getchar())x=x*10+c-‘0‘;
}
int f[50005],s[50005],fs[50005],a[50005],pre[50005];
int main(){
freopen("stick.in","r",stdin);
freopen("stick.out","w",stdout);
int n,m;
in(n),in(m);
int i,j,L;
int l=0,r=0,mid,sum,tot;
for(i=1;i<=n;++i){
in(a[i]);
s[i]=s[i-1]+a[i];
l=max(l,a[i]),r+=a[i];
}
--l;
while(r-l>1){
mid=l+r>>1;
tot=0,sum=0;
for(i=1;i<=n;++i)
if(sum+a[i]<=mid)sum+=a[i];
else sum=a[i],++tot;
if(tot<=m)r=mid;
else l=mid;
}
L=r;
memset(f,0,sizeof(f));
int ans=0;
for(i=1;i<=n&&s[i]<=L;++i)f[i]=1;
for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[i])%P;
ans=f[n];
int p=1;
for(i=1;i<=n;++i){
while(s[i]-s[p]>L)++p;
pre[i]=p-1;
}
for(j=1;j<=m;++j){
for(i=1;i<=n;++i)f[i]=(fs[i-1]-fs[pre[i]])%P;
for(i=1;i<=n;++i)fs[i]=(fs[i-1]+f[i])%P;
ans=(ans+f[n])%P;
}
printf("%d %d",L,(ans+P)%P);
}