vijos1488 路灯改建计划(重庆一中高2018级信息学竞赛测验9) 解题报告

【问题描述】  
  
  在华师一的敏行路上,新建了若干漂亮的路灯,这给同学们晚上的出行带来很大的方便。但是,问题随之出现了。


  一天晚上,OI组的FHH 同学正往校门外走,忽然眼前一片漆黑,于是直接把眼镜都摔掉了,再也找不到。后来FHH 同学从学校管理处了解到昨晚路灯突然熄灭是因为电路不堪重负,导致空气开关跳闸。


  善于思考的FHH 同学考虑将路灯进行改建,以避免再次出现类似的问题。FHH同学仔细了解每盏路灯的耗电量a[i]与照明度z[i],已知共有 N 盏电灯,并且每盏电灯都可能有不同的耗电量与照明度,现在的问题是要把这 N 盏电灯分为 M 组,新分出的每组灯的耗电量(即是该组所有打开电灯的耗电量之和)不能超过该组的电灯数目的 T 倍,在满足这样的前提下使得照明度尽可能的大,最后算出 M 组的最大照明度的和。由于每组耗电量的限制,该组中的某些电灯可能不被使用,但是仍然应该算作该组灯的数目。特别注意的是电灯按顺序给出,只能把相邻的几盏灯分在一组。


  由于计算较为复杂,FHH 同学经过反复的计算仍然不能确定结果,现在就请你为他编写一个程序来解决这个问题。 
 
    
 【输入格式】  
  
  第一行3个整数,分别表示N、M 和 T。
  接下来的N行,每行两个整数,第i+1行表示a[i]和z[i]。


 
    
 【输出格式】  
   
  一个整数,表示最大照明度。
 
    
 【输入样例】   
   
5 2 2
1 1
2 2
3 3
4 4
5 5


 
    
 【输出样例】  
   
10
 
    
 【数据范围】  
   
  对于70%的数据,保证有:2<=N<=80,1<=M<=35,1<=T,a[i],z[i]<=35;
  对于全部的数据,保证有:2<=N<=160,1<=M<=50,1<=T,a[i],z[i]<=50。


做题思路(70分解法):根据题意,要将N盏电灯分为M组,并且每组中的电灯可以开,可以不开,这显然是分组背包问题,主要算法自然是动态规划,但需要注意分组背包问题的需要两次动态规划,分别是求组权值,即求每组中的电灯选择一些打开,在满足条件的情况下的最大照明度的和,和求将电灯分组时的最大照明度的和。

对于第一次动态规划,设状态函数f(i,j)表示前i个电灯选择一些,耗电量为j时的最大照明度的和,分析第i个电灯时,它可以开,可以不开,所以状态转移方程为f(i,j)=max{f(i-1,j),f(i-1,j-a[i])+z[i]} ,边界条件是f(0,0)=0。为了优化程序,可以先计算出组权值g[i][j],表示将第i个电灯到第j个电灯放在一组时满足条件的最大照明度的和,在第二次动态规划时,可以直接引用。对于最简单的计算组权值,就可以直接枚举第x个电灯到第y个电灯,每枚举一次,进行一次动态规划。

对于第二次动态规划,设状态函数f(i,j)表示前i个电灯,分为j组时满足条件的最大照明度的和,分析第j组时,最少要有1个电灯,最多只能有i-j+1个电灯(为了保证前j-1组,至少每组有1个电灯),所以状态转移方程为f(i,j)=max{f(i-x,j-1)+g[i-x+1][i] | 1<=x<=i-j+1} ,边界条件是f(0,0)=0。答案即为f(N,M)。

但这种做法在计算组权值时,本来已经要枚举从第x个路灯到第y个路灯了,在进行动态规划时又有两重循环,时间复杂度为O(N^4*M)。


解题思路(正解):由于直接枚举计算组权值时有四重循环,会导致超时,所以要考虑是否可以减少一重循环,或者将一重循环移在枚举进行动态规划外面来进行优化。因为第一次动态规划的状态函数里限制了前i个电灯和耗电量为j,所以其实在计算组权值时枚举的到第y个路灯的这一重循环,可以移在枚举进行动态规划的外面来,只需枚举起始的路灯编号x,在进行动态规划后,g[x][y]=f(y,T*(y-x+1))(注:这里的状态函数f(i,j)的i是从x开始枚举的,意义与之前的状态函数有所不同,但状态转移方程依然一样)。这样计算组权值的话,时间复杂度就优化为O(N^3*M)。


#include
#include
#include
#include
#include
using namespace std;
const int maxn=165;
const int inf=1000000010;
int N,M,T;
int a[maxn],z[maxn];
int g[maxn][maxn]; //g[i][j]表示将第i个电灯到第j个电灯放在一组时满足条件的最大照明度的和,即组权值
/*
f(i,j)表示从第x个路灯到第i个电灯选择一些,耗电量为j时的最大照明度的和
f(i,j)=max{f(i-1,j),f(i-1,j-a[i])+z[i]} 
边界:f(0,0)=0 
*/ 
int f[maxn][maxn*52]; 
int ready(int x)  //第一次动态规划
{
	memset(f,0,sizeof(f));
	for(int i=x;i<=N;i++)
	for(int j=0;j<=T*(N-x+1);j++)
	{
		int t1=f[i-1][j],t2=0;
		if(j-a[i]>=0)  t2=f[i-1][j-a[i]]+z[i];
		f[i][j]=max(t1,t2);
	}
}
/*
f(i,j)表示前i个电灯,分为j组时满足条件的最大照明度的和
f(i,j)=max{f(i-x,j-1)+g[i-x+1][i] | 1<=x<=i-j+1} 
边界:f(0,0)=0 
*/
int d[maxn][55];
void solve()  //第二次动态规划
{
	for(int i=0;i<=N;i++)
	for(int j=0;j<=M;j++)
	d[i][j]=-inf;
	d[0][0]=0;
	for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++)
	{
		int t=-inf;
		for(int x=1;x<=i-j+1;x++)
		t=max(d[i-x][j-1]+g[i-x+1][i],t);
		d[i][j]=t;
	}
	printf("%d\n",d[N][M]);
}
int main()
{
	//freopen("light.in","r",stdin);
	//freopen("light.out","w",stdout);
	scanf("%d%d%d",&N,&M,&T);
	for(int i=1;i<=N;i++)
	scanf("%d%d",&a[i],&z[i]);
	for(int i=1;i<=N;i++)
	{
		ready(i);
		for(int j=i;j<=N;j++)
		g[i][j]=f[j][T*(j-i+1)];  //计算组权值
	}
	solve();
	return 0;
}

考后反思:对于程序的时间优化还是要加强练习,在考试的时候总是不能准确地找到程序的时间优化方法。

你可能感兴趣的:(动态规划,竞赛测验)