做题思路(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;
}