题目链接:点击这里
题目大意:
有
n
n
n 根木棍, 第i根木棍的长度为
L
i
,
n
L_i,n
Li,n 根木棍依次连结了一起, 总共有
n
−
1
n-1
n−1 个连接处. 现在允许你最多砍断
m
m
m 个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小. 并将结果
m
o
d
10007
mod\ 10007
mod 10007
题目分析:
首先看到最大长度最小化很容易想到用二分求解,而方案数显然跟这个二分的答案有关系,所以我们先二分求解最小长度再用
d
p
dp
dp 求解方案数
设最小长度是
x
x
x ,
s
u
m
[
i
]
=
∑
j
=
1
i
l
e
n
[
i
]
sum[i]=\sum_{j=1}^ilen[i]
sum[i]=∑j=1ilen[i] ,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示前
j
j
j 个木棍呗分成了
i
i
i 组的方案数(注意这个转换,分成了
i
i
i 组相当于切了
i
−
1
i-1
i−1 个连接处)
我们能得到这样一个转移方程 :
d
p
[
i
]
[
j
]
=
∑
a
=
k
j
−
1
dp[i][j]=\sum_{a= k}^{j-1}
dp[i][j]=∑a=kj−1 其中
k
k
k 为满足
s
u
m
[
j
]
−
s
u
m
[
k
]
<
=
x
sum[j]-sum[k]<=x
sum[j]−sum[k]<=x 的最小
k
k
k
s
u
m
[
j
]
−
s
u
m
[
k
]
<
=
x
sum[j]-sum[k]<=x
sum[j]−sum[k]<=x可以理解为将第
k
+
1
k+1
k+1 到
j
j
j 根木棍放在一组,然后将这一组放在由编号为
k
k
k 到
j
−
1
j-1
j−1 的木棍被分成
i
−
1
i-1
i−1 组的后面构成第
i
i
i 组
这个方程是的时间复杂度是
O
(
n
2
m
)
O(n^2m)
O(n2m) 的爆炸复杂度,考虑优化
我们发现可以提前预处理出来每次需要的
k
k
k 值,而且我们发现
s
u
m
[
i
]
sum[i]
sum[i] 是个单调递增的函数,因此
k
k
k 也是单调不减的,因此可以这样打表:
int k = 0;
for(int i = 1;i <= n;i++)
for(;k < i;k++)
if(sum[i]-sum[k] <= ans)
{
k_id[i] = k;
break;
}
我们发现
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 是由一段连续的数组转移过来的因此可以考虑前缀和优化,令
S
u
m
[
i
]
[
j
]
=
∑
k
=
0
j
d
p
[
i
]
[
k
]
Sum[i][j]=\sum_{k=0}^j dp[i][k]
Sum[i][j]=∑k=0jdp[i][k]
因此
d
p
[
i
]
[
j
]
=
S
u
m
[
i
−
1
]
[
j
−
1
]
−
S
u
m
[
i
−
1
]
[
k
−
1
]
dp[i][j]=Sum[i-1][j-1]-Sum[i-1][k-1]
dp[i][j]=Sum[i−1][j−1]−Sum[i−1][k−1] ,这样时间复杂度就变成了
O
(
n
m
)
O(nm)
O(nm)
我们又发现了我们的转移每次都是从
i
−
1
i-1
i−1 转移到
i
i
i ,因此我们可以优化掉
d
p
dp
dp 和
S
u
m
Sum
Sum 的第一维,而
S
u
m
Sum
Sum 可以直接用之前已经用完了无所事事的
s
u
m
sum
sum 数组存储即可
具体细节见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 5e4+5;
const int mod = 10007;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,len[maxn],dp[maxn],k_id[maxn],sum[maxn];
bool check(int u)
{
int sum = 0,cnt = 0;
for(int i = 1;i <= n;i++)
{
if(sum+len[i] > u)
{
sum = len[i];
cnt++;
}
else sum += len[i];
if(cnt > m) return false;
}
return cnt <= m;
}
int main()
{
n = read(),m = read();
int ans = inf,l = 0,r = inf;
for(int i = 1;i <= n;i++)
{
len[i] = read();
sum[i] = len[i]+sum[i-1];
l = max(l,len[i]);
}
while(l < r)
{
int mid = (l+r)>>1;
if(check(mid))
{
ans = min(ans,mid);
r = mid;
}
else l = mid+1;
}
int k = 0;
for(int i = 1;i <= n;i++)
for(;k < i;k++)
if(sum[i]-sum[k] <= ans)
{
k_id[i] = k;
break;
}
int res = (sum[n] <= ans);// 如果长度总和比ans还小就是只有一段
for(int i = 1;i <= n;i++)
{
if(sum[i] <= ans) dp[i] = 1;
sum[i] = (dp[i]+sum[i-1])%mod;
}
for(int i = 2;i <= m+1;i++)
{
for(int j = 1;j <= n;j++)
{
dp[j] = sum[j-1];
if(k_id[j]-1 >= 0) dp[j] = ((dp[j]-sum[k_id[j]-1])%mod+mod)%mod;
}
for(int j = 1;j <= n;j++)
sum[j] = (dp[j]+sum[j-1])%mod;
res = (res+dp[n])%mod;
}
printf("%d %d\n",ans,res);
return 0;
}