01背包,但至少取一种
f [ j ] 表示当点数之差为 j 时的最小反转次数,0<=j<=m (点数最大值之和)
我们可以不考虑每一块骨牌点差,仅考虑每次减去上面的(和翻转到上面的)数所得到的 j
则:f [ j ] = min ( f [ j - a [ i ] ] , f [ j - b [ i ] + 1),前者条件为 j>=a [ i ],后者条件 j>=b [ i ]
这样就可以避免负数组下标(当然另外一种也可以加上一个足够大的常数),并以m / 2为对称轴两边对称,也就是说 f [ j ] 与 f [ j + m / 2 ]含义是一样的
最后统计最小 j (j + m / 2)的 f [ ](!=INF) 即可
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 const int N=6010,INF=100010; 5 int a[N],b[N],f[N<<1],m,n; 6 int max(int x,int y){ 7 return x>y?x:y; 8 } 9 int min(int x,int y){ 10 return x<y?x:y; 11 } 12 int main(){ 13 int j,judge,t,ans; 14 t=m=0; 15 scanf("%d",&n); 16 for (int i=1;i<=n;i++){ 17 scanf("%d %d",&a[i],&b[i]); 18 m+=max(a[i],b[i]); 19 t+=a[i]+b[i]; 20 } 21 for (int j=m;j>=1;j--) 22 f[j]=INF; 23 f[0]=0;//边界,在取ans时取不到 24 for (int i=1;i<=n;i++){ 25 for (int j=m;j>=0;j--){ 26 int tmp=INF;//最优取值,强制取至少一种 27 if (j>=a[i]) tmp=f[j-a[i]]; 28 if (j>=b[i]) tmp=min(tmp,f[j-b[i]]+1); 29 f[j]=tmp; 30 } 31 } 32 int num=t>>1; 33 for (int j=num;j;j--){ 34 ans=min(f[t-j],f[j]); 35 if (ans!=INF){ 36 printf("%d",ans); 37 break; 38 } 39 } 40 return 0; 41 }