Description
有n根木棍, 第i根木棍的長度爲Li,n根木棍依次連結了一起, 總共有n-1個連接處. 現在允許你最多砍斷m個連接處, 砍完後n根木棍被分成了很多段,要求滿足總長度最大的一段長度最小, 並且輸出有多少種砍的方法使得總長度最大的一段長度最小. 並將結果mod 10007。。。
Input
輸入文件第一行有2個數n,m.接下來n行每行一個正整數Li,表示第i根木棍的長度.n<=50000,0<=m<=min(n-1,1000),1<=Li<=1000.
Output
輸出有2個數, 第一個數是總長度最大的一段的長度最小值, 第二個數是有多少種砍的方法使得滿足條件.
Sample Input
1
1
10
Sample Output
HINT
兩種砍的方法: (1)(1)(10)和(1 1)(10)
題解
很好的一道二分+優化dp的題.
首先看到最大的最小就想到二分,每次切儘量長的塊,看最後要切幾刀。二分完了就要dp了,他讓求最多切m塊的最優解,所以問什麼設什麼,dp[i][j]表示前i塊切j刀的最優解,所以dp[i][j]=sigma dp[k][j-1](sum[i]-sum[k]<=len).爲什麼這麼設就可以了呢?因爲二分時你已經算出了最小的長度了,不能再小了,所以只要滿足這個條件,分割的木棍中的最大長度一定是len這麼長。還沒完這麼做時間複雜度爲O(n*n*m)的足夠超時,空間複雜度也不行。所以要進行優化,發現j只與j-1有關,所以可以用滾動數組把這一維優化掉,由於i是單調遞增的所以k也是單調遞增的(即最前面的sum[i]-sum[k]<=len的k的位置是單調的),所以就可以用一個前綴和的思想,算出一個狀態最前面的k是什麼,這個可以預處理出來,這樣就可以把這個問題解決了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=50010;
const int mod = 10007;
int sum[maxn],a[maxn],f[maxn][3],g[maxn][3],pos[maxn],maxx;
int n,m;
inline bool pan(int x){
int ans=0,last=0;;
if(maxx>x) return false;
for(int i=1;i<=n;i++){
if(sum[i]-last>x) {
ans++;
last=sum[i-1];
}
if(ans>m) return false;
}
return true;
}
inline int binary(){
int l=0,r=sum[n];
while(l<=r){
int mid=(l+r)>>1;
if(pan(mid)) r=mid-1;
else l=mid+1;
}
if(pan(l)) return l;
else return r;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
maxx=max(maxx,a[i]);
sum[i]=sum[i-1]+a[i];
}
int len=binary(),ans=0;
for (int i=1;i<=n;i++)
if (sum[i]<=len) f[i][0]=1;
else break;
for(int i=1;i<=n;i++){
if(sum[i]<=len) continue;
for(int j=i-1;j>=0;j--)
if(sum[i]-sum[j]>len){
pos[i]=j+1;
break;
}
}
int x=0;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
g[j][x]=g[j-1][x]+f[j][x];
}
x^=1;
for(int j=1;j<=n;j++)
f[j][x]=(g[j-1][x^1]-g[max(pos[j]-1,0)][x^1])%mod;
(ans+=f[n][x])%=mod;
}
printf("%d %d\n",len,ans);
return 0;
}