NC51170 石子合并
经典的区间\(DP\)问题。
区间\(DP\)特征:从小区间逐渐向大区间扩展递推。
题解
贪心只能处理“任取两堆”,而不能处理“相邻两堆”。任取两堆的题目就是合并果子。
状态表示:\(f(i,j)\):合并区间\([i,j]\)的最小代价
状态转移:\(f(i,j) = \begin{cases}0,i = j\\
\underset{i≤k<j}{min}(f(i,j),f(i,k)+f(k+1,j)+sum(i,j)),i≠j
\end{cases}\)
\(sum(i,j)\)为区间\([i,j]\)内石子的个数。
相当于将\([i,j]\)拆成两个区间\([i,k]\)和\([k+1,j]\),两个区间各自合并石子需要\(f(i,k)\)和
\(f(k+1,j)\)的代价,将两个区间合并需要\(sum(i,j)\)的代价。
\(\color{red}{Wrong Answer}\)代码:
const int N=310;
int f[N][N];
int a[N];
int sum[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
if(i == j) f[i][j]=0;
else
for(int k=i;k<j;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}
cout<<f[1][n]<<endl;
//system("pause");
}
在考虑如何递推时,应考虑如下几个方面:
- 是否能覆盖全部状态?
- 求解后面状态时是否保证前面状态已经确定?
- 是否修改了已经确定的状态?
也就是说,在考虑递推顺序时,务必参考动态规划的对象具有的性质。
既然之前说过我们需要枚举k来划分i和j,那么如果通过枚举i和j进行状态转移,很显然某些k值时并不能保证已经确定过所需状态。
- \(i=1 ~ 10\)
- \(j=1 ~ 10\)
- \(k=1 ~ 9\)
当\(i=1,j=5,k=3\)时,显然状态\(f(k+1,j)\)没有计算。
解决办法也很简单,将\(i\)倒序循环即可。
\(\color{green}{Accepted}\)代码:
const int N=310;
int f[N][N];
int a[N];
int sum[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
memset(f,0x3f,sizeof f);
for(int i=n;i>=1;i--)
for(int j=i;j<=n;j++)
{
if(i == j) f[i][j]=0;
else
for(int k=i;k<j;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}
cout<<f[1][n]<<endl;
//system("pause");
}