对于这道题目的两问,第一问直接二分答案求出最短长度。关键在于第二问应当如何求:建立dp方程,dp[i][j]代表到第i个分界线,切了j次(强制在第i处切一刀、这样就不会对后面的状态产生影响)。状态转移的方程即是当前分界线枚举上一条分界线在哪里,上一条分界线与当前线之间如果相差不超过之前二分出来的答案,就可以判定合法,方案数累加。因为注意到合法的分界线必然是一段连续区间,且单调右移不减,所以使用一个队列来维护队列内的元素总值。虽然让我感到非常玄学的是明明数据很小,我的数组一开小了就WA?不是很理解……
#include <bits/stdc++.h> using namespace std; #define maxn 5000000 #define maxm 300000 #define mod 10007 #define INF 99999999 int m, maxx, fans, ans, a[maxn], sum[maxn], dp[maxm][3], n, pre = 0, now = 1; int q[maxn], tot, head, tail; bool mark[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < ‘0‘ || c > ‘9‘) { if(c == ‘-‘) k = -1; c = getchar(); } while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); return x * k; } bool check(int x) { int cnt = m; int tem = 0; for(int i = 1; i <= n; i ++) { if(tem + a[i] <= x) tem += a[i]; else { if(!cnt) return false; tem = a[i], cnt --; } } return true; } void solve() { int l = maxx, r = sum[n]; while(l <= r) { int mid = (l + r) >> 1; if(check(mid)) ans = mid, r = mid - 1; else l = mid + 1; } } void DP() { dp[0][pre] = 1; for(int i = n - 1; i >= 1; i --) { if(sum[n] - sum[i] <= ans) mark[i] = true; else break; } bool flag = false; for(int i = 1; i <= m; i ++) { head = 1, tail = 0; q[++ tail] = i - 1, tot = dp[i - 1][pre]; for(int j = i; j <= n; j ++) { while(head <= tail && sum[j] - sum[q[head]] > ans) tot = (tot - dp[q[head]][pre] + mod) % mod, head ++; dp[j][now] = tot, dp[j][now] %= mod; if(dp[j][pre]) q[++ tail] = j, tot += dp[j][pre], tot %= mod; if(mark[j]) { fans += dp[j][now]; fans %= mod; } } now ^= 1, pre ^= 1; } } int main() { n = read(), m = read(); for(int i = 1; i <= n; i ++) { a[i] = read(), sum[i] = sum[i - 1] + a[i]; maxx = max(a[i], maxx); } solve(); cout << ans << " "; DP(); cout <<fans << endl; return 0; }