情书

[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;
}
原文地址:https://www.cnblogs.com/MYCui/p/13898514.html