情书

[LuoGu]P2112 鸿雁传书

(看到标题的你在想什么?)

这道题讲了小明给小红写情书的故事.

题意简化:

给你\(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;
}
posted @ 2020-10-29 19:42  MYCui  阅读(93)  评论(0编辑  收藏  举报