情书
(看到标题的你在想什么?)
这道题讲了小明给小红写情书的故事.
题意简化:
给你\(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;
}