(看到标题的你在想什么?)
这道题讲了小明给小红写情书的故事.
题意简化:
给你(n)个字符串,以及(k),要求把字符串分为(k)行,使得每一行的字母方差最小(不能改变每个字符串的相对位置)
思路
这个字符串屁用没得。我们只需要知道字符串的长度就行了,然后这个就变成了一个序列划分的问题。
要求把原序列分成(k)个子序列,使得这(k)个子序列的方差最小,果断dp。
方差 ?
设整个序列平均数为(a),则(a)=(frac{1}{n}) (sum_{i=1}^{i=n}{a_i})((a_i)为原序列第(i)项的权值)
方差(s^2) =(frac{1}{n}) (sum_{i = 1}^{i = n}{(a_i - a)}^2)
不妨展开式子:(s^2) = (frac{1}{n}) {(sum_{i=1}^{i=n})(a_i^2) + (n*a^2) - (sum_{i=1}^{i=n})(2 * a * a_i)}
发现(a)肯定是不会变的.....这个变量就可以简单处理了
同时关于方差还有一个式子:(s^2) = (sum_{i = 1}^{i = n}{a[i]^2} * n - (sum_{i = 1}^{i = n}{a[i]})^2) (* frac{1}{n^2})
然后我们就好处理了。
(DP[i][j])表示前(i)个字符串丢进前(j)行可以获得的最小方差(或许我们也暂时不能称为方差,因为没有除以n)。
然后我们可以枚举分割点,假设为(k)是分割点。也就是,第(k)个字符串到第(i)个字符串丢进第j行。用一个前缀和比较好维护!求使得方差最小的分割点转移即可。
于是动态规划方程为:(DP[i][j + 1] = min(DP[i][j + 1] , DP[k][j] + (sum[i] - sum[k]- avg)^2))
((sum[i],sum[j])是前缀和,(avg)是整个序列的平均数 ,根据分析这个平均数是不会变的)
同时我们也没必要每次除以长度,因为这样子显然不好处理,在最后的时候除以一个(m)即可
同时,说一个偷懒的好技巧,对于这种求平方的东西,我们可以用一个函数来完成,就比如我写的:
double Double(double x)
{
return x * x;
}
这样子可以减少代码长度并且可以让你的式子看起来不那么冗长,更加美观!
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m;
double avg, a[N], sum[N], dp[N][N];
char str[N];
double Double(double x)
{
return x * x;
}
void prepare()
{
for(int i = 1 ; i <= n ; i ++)
{
cin >> str;
sum[i] = sum[i - 1] + strlen(str);//前缀和
}
avg = sum[n] / m;
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= m ; j ++)
dp[i][j] = 1e8;//初始化
for(int i = 1 ; i <= n ; i ++)
dp[i][1] = Double((sum[i] - avg));//预处理出只放入第一个的情况
}
int main(){
cin >> n >> m;
prepare();
for(int j = 1 ; j <= m ; j ++)
for(int k = j ; k <= n ; k ++)//分割点
for(int i = k + 1 ; i <= n ; i ++)//转移
dp[i][j + 1] = min(dp[i][j + 1], dp[k][j] + Double((sum[i] - sum[k] - avg)));
printf("%.1lf", dp[n][m] / m);
return 0;
}