Cpp环境【CQYZOJ3145】【CQOI2916】学生宿舍依法集会权遭受侵害案例

【问题描述】   

  新的学生宿舍开放了,它由m栋建筑物构成,标号为1到m。开始时,学生宿舍都是空的,很快 有N个学生搬进去了。刚好每天搬进去一个。 每次有新同学搬进宿舍,那栋建筑将会举行一个大型的 party。party 的噪声和该建筑物里的 学生的数量相等。宿舍管理员不喜欢噪声,所以他们会不定期的清空某栋建筑物。清空的方法就是把 该栋建筑物的学生全部赶到另外的学生宿舍(这 m 栋宿舍以外的地方)。但是管理员最多只能清空 k 次。求这n天噪音之和的最小值。

【输入】   

  第一行三个整数n,m,k。
  接下来有n行,每行一个整数bi(1<=bi<=m),表示第i天有一个学生搬进了bi栋宿舍。

【输出】

  一个整数,表示这n天噪音之和的最小值。

【输入样例】

5 1 2
1 1 1 1 1

【输出样例】

7

【样例解释】

  在第一天和第三天之后执行清空操作。这样每天的噪声为 1,1,2,1,2,所以噪声之 和为7。

【数据范围】  

   对于20%的数据满足:1<=n<=20
  对于50%的数据满足:1<=n<=1000,1<=m<=100,1<=k<=50
  对于100%的数据满足:1<=n<=1000000,1<=m<=100,1<=k<=5

【原题传送矩阵】

重庆八中原题传送矩阵
重庆一中原题传送矩阵

【思路梳理】

  观察此题数据规模可以发现:n最大可以达到1000000,着实不小,而对于m和k来说却只有100、500,说明我们可以考虑使用动态规划。设置如下的状态函数:d[i][j]=对前i栋宿舍进行j次清扫所能够产生的最小值。由此一来就可以绕开n特别大而导致的超时超空间的问题,使得时间复杂度只有这里写图片描述
  
  仔细分析问题后不难发现:学生进入宿舍的先后次序并不关键,例如A同学在第一天进入1宿舍,B同学在第二天进入了2宿舍,C同学在第三天进入了1宿舍,对此进行0次清扫,那么就有:总噪音值=(1+2)+1=4,改变这个顺序使得A、C先进入1宿舍,B在第三天进入2宿舍,可以发现总的噪音值仍然为4。那么我们在输入时就没有必要将学生们按照进入的先后顺序存储,直接记录每一个宿舍最后有多少个人即可。方便我们推导状态转移方程。
  
  当我们当前在考虑第d[i][j]时,显然我们可以对第i栋建筑进行0次清扫(不清扫)、1次清扫……j次清扫,那么显然对前i-1栋宿舍我们就进行了j次清扫、j-1次清扫……0次清扫,不难得出状态转移方程如下:
  

d[i][j]=min { d[i-1][x]+g[i][j-x] | x=对前i-1栋宿舍进行的清扫次数,0<=x<=j }
g[i][j]=对第i栋宿舍进行j次清扫所能够带来的最小噪音值

  
  然后考虑如何清扫能够带来最小的噪音值,即g数组的值。显然每一次分割的时候我们均分是更好的选择,故考虑对第i栋建筑进行j次清扫时,应尽可能相等地分成j+1个部分,每一次分割都是一次等差数列求和(这里写图片描述。如果分割后有余数,就应该将他们平均分到前面几次清扫的尾部去,所以不难写出如下的程序。

#include<cstdio>
#include<iostream>
#include<algorithm>
#define maxn 1000005
#define maxm 105
#define maxk 505
#define inf 200000000000005ll
using namespace std;
int a[maxn],n,m,t,num[maxn];
long long g[maxm][maxk],d[maxm][maxk];

void read(int &n)
{
    n=0;
    char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9')
    {
        n=n*10+ch-'0';
        ch=getchar();
    }
}

long long calc(int i,int j)//计算对第i个宿舍清理j次的最小权值 
{
    j++;//j加1的原因很简单,此时相当于是在添加j个挡板,将整个宿舍的人按照先后顺序分成j+1份
    //当1个宿舍只清理0次时,显然分且仅分成了1组,j如果等于0,t就无意义了
    //显然对于每一栋宿舍应该尽可能地均分 
    if(j>num[i])    return inf;//在一天中进行多次清扫显然不是合法行为,侵犯了公民依法集会的权利
    long long t=num[i]/j,cnt=0,p=num[i]%j;
    return (t+1)*(t+2)/2*p+t*(t+1)/2*(j-p);
}

void dp()
{ 
    //d[i][j]=min {d[i-1][x]+calc(i,j-x) | 0<=x<=j }
    //任何时候,”对前i栋建筑进行j次清扫“的值不会比“对i栋建筑进行j-1次清扫”的值更差
    for(int i=1;i<=m;i++)
    for(int j=0;j<=t;j++)
    {
        long long b=inf;
        for(int k=0;k<=j;k++)
            b=min(b,d[i-1][k]+calc(i,j-k));
        d[i][j]=b;
    }
    cout<<d[m][t]<<endl;
}

int main()
{
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=n;i++)   read(a[i]),num[a[i]]++;
    //顺序不重要,a数组不是必须,我们只需要统计进入每栋楼的人数即可
    dp();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值