二分/DP
真是一道好题!
第一问很简单的二分……
第二问一开始我想成贪心了,其实应该是DP的= =
然后没有注意……又MLE又TLE的……这题要对DP进行时空两方面的优化!!
使用前缀和,令 Sum[i] 为前 i 根木棍的长度和。
令 f[i][j] 为前 i 根木棍中切 j 刀,并且满足最长长度不超过 j 的方案数,那么:
状态转移方程: f[i][j] = Σ f[k][j-1] ((1 <= k <= i-1) && (Sum[i] - Sum[k] <= Len))
这样的空间复杂度为 O(nm) ,时间复杂度为 O(n^2 m) 。显然都超出了限制。
下面我们考虑 DP 的优化。
1) 对于空间的优化。
这个比较显然,由于当前的 f[][j] 只与 f[][j-1] 有关,所以可以用滚动数组来实现。
f[i][Now] 代替了 f[i][j] , f[i][Now^1] 代替了 f[i][j-1] 。为了方便,我们把 f[][Now^1] 叫做 f[][Last] 。
这样空间复杂度为 O(n) 。满足空间限制。
2) 对于时间的优化。
考虑优化状态转移的过程。
对于 f[i][Now] ,其实是 f[mink][Last]...f[i-1][Last] 这一段 f[k][Last] 的和,mink 是满足 Sum[i] - Sum[k] <= Len 的最小的 k ,那么,对于从 1 到 n 枚举的 i ,相对应的 mink 也一定是非递减的(因为 Sum[i] 是递增的)。我们记录下 f[1][Last]...f[i-1][Last] 的和 Sumf ,mink 初始设为 1,每次对于 i 将 mink 向后推移,推移的同时将被舍弃的 p 对应的 f[p][Last] 从 Sumf 中减去。那么 f[i][Now] 就是 Sumf 的值。
这样时间复杂度为 O(nm) 。满足时间限制。
1 /************************************************************** 2 Problem: 1044 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:4152 ms 7 Memory:4396 kb 8 ****************************************************************/ 9 10 //BZOJ 1044 11 #include<vector> 12 #include<cstdio> 13 #include<cstring> 14 #include<cstdlib> 15 #include<iostream> 16 #include<algorithm> 17 #define rep(i,n) for(int i=0;i<n;++i) 18 #define F(i,j,n) for(int i=j;i<=n;++i) 19 #define D(i,j,n) for(int i=j;i>=n;--i) 20 #define pb push_back 21 using namespace std; 22 inline int getint(){ 23 int v=0,sign=1; char ch=getchar(); 24 while(ch<‘0‘||ch>‘9‘){ if (ch==‘-‘) sign=-1; ch=getchar();} 25 while(ch>=‘0‘&&ch<=‘9‘){ v=v*10+ch-‘0‘; ch=getchar();} 26 return v*sign; 27 } 28 const int N=1e5+10,INF=~0u>>2,P=10007; 29 typedef long long LL; 30 /******************tamplate*********************/ 31 int n,m,ans,a[N],f[N][2],pos[N],sumf[N][2]; 32 LL s[N]; 33 bool check(int len){ 34 int cnt=0,sum=0; 35 F(i,1,n){ 36 if (a[i]>len) return 0; 37 if (sum+a[i]>len){ 38 cnt++; sum=a[i]; 39 }else sum+=a[i]; 40 } 41 return cnt<=m; 42 } 43 int main(){ 44 #ifndef ONLINE_JUDGE 45 freopen("1044.in","r",stdin); 46 freopen("1044.out","w",stdout); 47 #endif 48 n=getint(); m=getint(); 49 F(i,1,n) {a[i]=getint();s[i]=s[i-1]+a[i];} 50 51 int l=0,r=s[n],mid; 52 while(l<=r){ 53 mid=l+r>>1; 54 if (check(mid)) ans=mid,r=mid-1; 55 else l=mid+1; 56 } 57 printf("%d ",ans); 58 F(i,0,n) sumf[i][0]=1; 59 int way=0; 60 F(j,1,m+1){ 61 int now=j&1; 62 sumf[0][now]=0; 63 F(i,1,n){ 64 if (!pos[i]) 65 pos[i]=pos[i-1]; while(s[i]-s[pos[i]]>ans) pos[i]++; 66 f[i][now]=(sumf[i-1][now^1]-sumf[pos[i]-1][now^1]+P)%P; 67 sumf[i][now]=(sumf[i-1][now]+f[i][now])%P; 68 } 69 way=(way+f[n][now])%P; 70 } 71 printf("%d\n",way); 72 return 0; 73 }
1044: [HAOI2008]木棍分割
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 2008 Solved: 725
[Submit][Status][Discuss]
Description
有n根木棍, 第i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果mod 10007。。。
Input
输入文件第一行有2个数n,m. 接下来n行每行一个正整数Li,表示第i根木棍的长度.
Output
输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.
Sample Input
3 2
1
1
10
Sample Output
10 2
HINT
两种砍的方法: (1)(1)(10)和(1 1)(10)
数据范围
n<=50000, 0<=m<=min(n-1,1000).
1<=Li<=1000.