学生宿舍

【问题描述】  
  
  新的学生宿舍开放了,它由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解释:在第一天和第三天之后执行清空操作。这样每天的噪声为1,1,2,1,2,所以噪声之和为7. 
 
    
 【数据范围】  
   

1<=n<=1000000,1<=m<=100,1<=k<=500


  这次考试最坑的一个题了吧,考场上最先想的用f(i,j,k)来描述前i天,前j个寝室清理k次的最小噪音和,然后,就卡在转移了,而且面对n这么大的规模我也只能一脸懵逼,显然这样的状态是不行的,然后想省去i这一维,却卡在转移上了,然后想搜素,结果每一天的选择都特别多,而且天数又是辣么的大,又歇菜了,最后胡乱写了一个程序证明我都是做完的的交了上去,结果当然是GG了。

  后来考完了HB说要二分答案+贪心,.smg???二分答案猜一个标准limit,然后说只要有一个寝室在这一天大于了这个标准就该清理,如果清理完了总的次数小于了k,则在左边继续猜limit,否则在右边猜limit,用坠吼最后猜到的limit,模拟一次把噪声值算出来就是答案。大家都觉得不科学。

  但是后来CXW那坨(原谅我不会量词)和YKL等人研究了一中午,说这个题应该是动态规划解的,因为Mr.he错误的认为了所有寝室都必须达到了limit才需要清理,但是事实上,如果把有些寝室的清空标准降低,或许可以得到更优的解。设f(i,j)表示i个宿舍清空j次的最小噪声和。

  考虑第i个寝室,可以清空0次,问题转化为求f(i-1,j),f(i,j)=f(i-1,j)+c[i][0] //这里c[i][j]表示将i号宿舍清空j次所能得到的最小噪声和

  ......1次f(i-1,j-1)+c[i][1].....

 即f(i,j)=min{f(i-1,j-k)+c[i][k] | 0<=k<=j };

 关于c[i][j]的计算,其实可以预先通过输入知道一个寝室在n天中总共被x人住过,然后就是把x人分成j+1组使得噪声值的和最小,根据噪声值的算法容易想见:

c[i][j]=1+...+x/(j+1)+x%(j+1)*(x/(j+1)+1);

代码实现:

#include<cstdio>
#include<cstdlib>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#define maxn 1005 
using namespace std;
typedef long long LL;
const LL inf = 200000000000000ll;
LL f[maxn][maxn],c[maxn][maxn];
/*
	f(i,j)表示前i个寝室,清空j次的最小噪音和
	
	f(i,j)=min{ f(i-1,j-x)+c[i][x] | 0<=x<=j} //其中c[i][x]表示对第i栋建筑清空x次的最小噪音
	
	f(0,j)=0;
	
	Ans=f(m,k); 
*/
int n,m,k;
int a[maxn];//a[i]表示i号寝室住过的总人数 
void ready()//计算c[i][j] 
{
	for(int i=1;i<=m;i++)
	for(int j=0;j<=k;j++)
	{ 
		LL p=a[i]/(j+1),q=a[i]%(j+1),s=0;
		for(int r=1;r<=p;r++)
		s+=r;
		
		c[i][j]=s*(j+1)+q*(p+1); 
	}
}

void dp()
{
	ready();
	
	for(int i=1;i<=m;i++)
	for(int j=0;j<=k;j++)
	{
		LL t=inf,tt=inf;
		for(int x=0;x<=j;x++)
		{
			t=min(t,f[i-1][j-x]+c[i][x]);
			tt=min(t,tt);
		}
		f[i][j]=tt;
	}
	
	cout<<f[m][k]<<'\n';
}

void _read(int &x)
{
	x=0;
	bool flag=1;
	char t=getchar();
	while(t<'0' || t>'9')
	{
		if(t=='-')flag=0;
		t=getchar();
	}
	
	while(t>='0' && t<='9')
	{
		x=x*10+t-'0';
		t=getchar();
	}
	
	if(!flag) x=-x;
}

int main()
{
	//freopen("dorm.in","r",stdin);
	//freopen("dorm.out","w",stdout);
	
	scanf("%d%d%d",&n,&m,&k);
	memset(a,0,sizeof(a));
	for(int i=1;i<=n;i++)
	{
		int x;
		_read(x);
		a[x]++;
	}
		
	dp();
	
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值