coci2014 contest#1 T5-zabavz—— dp

来源:互联网 发布:python 英文词性标注 编辑:程序博客网 时间:2024/05/16 11:46

coci2014 contest#1 T5
https://www.luogu.org/problem/show?pid=T10946

题目描述
题目大意:学校新建了M栋宿舍,有N个学生入住,每天新入住1人。当一个宿舍有新人入住时候就开个吵人的paty,吵闹度为宿舍的所有学生人数。宿管们都烦吵闹,他们有权让一个宿舍的所有学生全搬走,但只能用不超过k次。求最小的的吵闹度。

输入格式:
输入第一行三个数N([1,1000000],M[1,100],K[1,500],
接下来一行,N个用空格分割的数,第i个数代表第i天学生入住的楼号。
输出格式:
输出一个数,代表最小的吵闹度
输入入样例#1:
5 1 2 
1 1 1 1 1输出样例#1:
7输入样例#2:11 2 31 2 1 2 1 2 1 2 1 2 1 2 1输出样例#2:
18题目分析:

首先,我们需要注意到,进入大楼的学生的顺序是无关紧要的:所有重要的是有多少学生进入某个建筑,因为某个建筑的各方完全独立于其他建筑物。因此,我们可以假定我们给出M个数字:每个建筑物进入的学生人数。

让我们观察一座建筑物,并假设它将被清空P次,看看如何最佳地清空。让我们假设我们第一次清空它是在x1个学生搬进去之后,第二次在新的x2学生搬进来之后,等等,直到最后一次我们在xP 学生之后,另外一名x(P + 1)的学生搬到这里,直到最后。

第一次清空之前的噪声水平为1 + 2 + ... + x1 = x1(x1 + 1)/ 2,类似公式在第一次为

清空,第二次和所有的休息直到结束。

因此,建筑物的总噪声水平为:

½* sum(xi(xi + 1)),对于i = 1 ... P + 1。

如果我们不看前面的1/2,这个噪声级别就等于它的总和xi*xi+xi,sum(xi)总和不变,等于移入该建筑物的学生总数。所以,我们需要最小化P + 1数字的平方和,并给出这些数字的总和。

从算术和二次均值不等式可以看出,当数字相等时,平方和总和最小。在这里,由于(在)可分割性而偶尔不可能实现,但是数字将“几乎相等”,换句话说,它们的差值是0或1。该分割和所需的总和可以容易地通过使用简单的公式在0(1)时间内计算。

根据一个建筑物和他固定清空次数,我们可以看到这个问题的解决途径。

现在可以通过动态规划来解决任务。如果dp(m,k)标志着我们可以在第一批m个建筑物中实现最低的噪声水平,其中k次将其排空,则计算该值,以便我们尝试排除第m个建筑物的所有可能的方式(让我们称之为P)并检查给定的噪声电平,即:

dp(m-1,k-P)+(第m个建筑物的解决方案,P次被清空,如上所述,是最小的噪声水平。最后的解决方案当然是dp(M,K)。该算法的复杂度为O(MK*K)。

必要的技能:数学问题分析,动态规划

类别:动态规划参考代码:

#include <cstdio>#include <algorithm>#include<iostream>using namespace std;const int MM = 101;const int KK = 505;int bilud[MM];//每栋楼里面的人数 long long dp[MM][KK];inline long long noise(int n) {//求出一段的噪音     return (long long)n * (n + 1) / 2;}inline long long seg(int n, int k) {//把n个人搬走平K次,分成k+1份,不能平分时候两个数的差值不超过1    k++;    int div= n / k;    int mod= n % k;    return mod* noise(div+1) +       (k-mod)* noise(div);}int main() {    int N, M, K;    scanf("%d%d%d", &N, &M, &K);    while (N--) {        int zgrada;        scanf("%d", &zgrada);        ++bilud[zgrada];//统计这个宿舍最终的入住人数     }    for (int m = 1; m <= M; ++m)        for (int k = 0; k <= K; ++k) {//dp[m][k]前m栋楼搬了k次             dp[m][k] = seg(bilud[m], 0) + dp[m - 1][k];            for (int i = 1; i <= k; ++i)                dp[m][k] = min(dp[m][k],//这栋楼搬了多少次。                         seg(bilud[m], i) + dp[m - 1][k - i]);            }    printf("%lld\n", dp[M][K]);}