CF 590D
题目大意:
给出一个长度为N的序列,同时你有S次机会交换任意两个相邻位置的数,希望前K个数的和最小。
错误思路。。
- 很容易想到状态为: f[i][j]: 前i个数交换了j次的最小和。
然后就在 f[k][j]: j∈[0,S] 中找到一个最小的值就是答案了。
想法总是很好的。但是转移怎么办???
按照正常的想法可以这样:
f[i][j]=min{f[i][j],f[i][j−k]+(a[i+k]−[i])}
这个方程表示如果第i个数可以被交换,则显然交换j次可以最远更新到a[i+j],然后交换a[i+k]与a[i];
But!!
Dp 是不能有后效性的!!
而当我们交换若干次后,a[i+k]可能早已不是以前a[]中的数值,即它可能已经被交换过了。所以,这个方程是错的。
正解
状态不变,但是方程要变为f[i+1][j+k-(i+1)]=min(f[i+1][j+k-(i+1)],f[i][j]+a[k]);
- 由当前的f[i][j]来更新f[i+1][j+k-(i+1)]【小->大】
- 为了没有后效性,一层循环由小到大枚举,一层由大到小枚举。
Attention:
题目中S的范围很大,10^9但是并不需要这么多,事实上对于长度为n的序列,最多只能交换n*(n-1)次,这个很好理解,也可以打表确定一下;
Code:
#include <stdio.h>
#include <string.h>
#define INF 99999999;
int n,k,s,ans;
int a[151],f[151][150*150];
int min(int a,int b){return a<b?a:b;}
int main()
{
scanf("%d%d%d",&n,&k,&s);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int k=1;k<=n;k++)
{
for(int i=k-1;i>=0;i--)
{
for(int j=0;j<=k*i;j++)
{
if(f[i][j]!=0x3f3f3f3f)
{
f[i+1][j+k-(i+1)]=min(f[i+1][j+k-(i+1)],f[i][j]+a[k]);
}
}
}
}
memset(&ans,127,sizeof(int));
for(int j=0;j<=min(s,(n*(n)));j++)
{
ans=min(ans,f[k][j]);
}
printf("%d\n",ans);
return 0;
}
P.S. 自动忽略ans的赋值就好啦。。