Problem . cut
Background:
伐木工Zxr非常懒惰了,所以他在伐木的时候只会找准木头被虫蛀过的腐朽的地方砍下去。
Description:
一块有N-1处被虫蛀过的地方,假设被虫蛀过的地方长度不计。这n-1个虫蛀将木头分成了n块,题目将依次给出这n块木头的长度。懒惰的zxr最多只想砍m次,并且希望可以借此把这块木头分得尽量均匀,即希望使砍伐后连续的木块中最长的尽量短。这个时候聪明的你跳了出来“我不仅能算出这个最短长度,我还能算出方案数!”
Input:
第一行两个数n,m。
接下来一行n个数分别表示这N块木头的长度。
Output:
一行两个数分别表示最短的最长长度和方案数。
Sampleinput:
32
1110
Sampleoutput:
102
Hint:
两种砍的方法: (1)(1)(10)和(1 1)(10)。
对于30%的数据,n<=10,
对于100%的数据,n<=50000,
0<=m<=min(n-1,1000),1<=Li<=1000.
题解 :
首先这道题一共有两个问题,第一问求最长长度的最短值,很明显用二分,只需要每次二分的时候check()一下,然后我们就可以得到这个长度len。
然后来分析第二个问题,第二个问题求方案数,一共有n块,最多切m刀,所以dp数组中需要包含着两个元素,所以我们就设我们的dp数组dp[ i ][ j ]表示:
前i段切了j刀得到的方案数;
状态转移方程:
dp[ i ][ j ] = sigma( dp[ k ][ j - 1 ] ) ( 1 <= k <= i - 1 ),我们在设一个前缀数组sum[ i ],所以k还需要满足sum[ i ] - sum[ k ] <= len。
我们再来看一下数据范围,n是50000,而m是1000,所以我们的空间复杂度和时间复杂度都超限制了,所以需要优化:
优化空间:
我们发现每一个状态j都只与j - 1有关,所以我们可以设一个now,表示当前状态即j,再设一个last表示j - 1,last = now ^ 1,这样就将空间复杂度优化了。
优化时间:
我们来想一下,我们每次更新dp[ i ][ j ],都需要用到前面的dp[ k ][ last ]的和,所以我们不妨用一个sumf来记录这个和,这就省去了循环k的时间,但是我们还需要满足sum[ i ] - sum[ k ] <= len,所以我们sumf中还需要减去一些不需要的k,我们不难发现sum[ i ]递增的数组,所以我们需要的满足sum[ i ] - sum[ k ] <= len的最小k值mink的值也不会是递减的,所以我们只需要每次对于i,mink的值向后推移,同时在sumf中将dp[ mink ][ last ]的值减去就可以了,这样我们的时间复杂度就优化为O( mn )。
代码:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int N = 50001, M = 1001, mod = 10007; int dp[N][2], sum[N], n, m, len, tot, a[N]; bool check(int val){ int res = 0, g = 0; for (int i = 1; i <= n; i++){ if (res + a[i] <= val){ res += a[i]; continue; } else { if (a[i] > val) return false; g++; if (g > m) return false; res = a[i]; } } return true; } void minlen(){//二分求len int ll = 1, rr = tot; while (ll != rr){ int mid = (ll + rr) >> 1; if (check(mid)) rr = mid; else ll = mid + 1; } len = rr;//必须是len = rr,不是len = mid或者len = ll! } int main(){ freopen("cut.in", "r", stdin); freopen("cut.out", "w", stdout); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++){ scanf("%d", &a[i]); tot += a[i]; sum[i] = sum[i - 1] + a[i]; } minlen(); int now = 0, last = 1, ans = 0,sumf, mink; for (int j = 0; j <= m; j++){ sumf = 0; mink = 1; for (int i = 1; i <= n; i++){ if (j == 0){ if (sum[i] <= len) dp[i][now] = 1; else dp[i][now] = 0; } else { while (mink < i && sum[i] - sum[mink] > len){ sumf -= dp[mink][last]; sumf = (sumf + mod) % mod;//模一个mod是因为有可能sumf会减成负数 mink++; } dp[i][now] = sumf; } sumf += dp[i][last]; sumf %= mod; } ans += dp[n][now]; ans %= mod; now ^= 1; last = now ^ 1; } printf("%d %d", len, ans); return 0; }