- 标签动态规划
- 难度提高+/省选-
- 通过/提交282/964
题目描述
多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的
上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|。例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。
编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。
对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。
输入输出格式
输入格式:
输入文件的第一行是一个正整数n(1≤n≤1000),表示多米诺骨牌数。接下来的n行表示n个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数a和b,且1≤a,b≤6。
输出格式:
输出文件仅一行,包含一个整数。表示求得的最小旋转次数。
输入输出样例
4 6 1 1 5 1 3 1 2
1
问题分析:
很明显,对于当前骨牌只有翻与不翻两种选择,就像是01背包取物品的时候的取与不取是相通的,那么我们就可以尝试用01背包解决,如果这个牌不翻,会是什么状态?如果翻了是什么状态?选择的标准就是犯的次数最少。
代码一:
未压缩空间版:
#include<iostream> using namespace std; #include<cstdio> #include<cstring> #define INF 1010 #define Q 2000 int n,N,w[INF]; int f[INF][INF*10]; void input() { int a,b; scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d%d",&a,&b); w[i]=a-b; } } void DP() { N=5*n;/*进行平移的数,防止数组越界,5*n就是最大的差值了*/ memset(f,127,sizeof(f)); f[1][w[1]+N]=0;/*差值全部定义成a-b,那么对于前i张牌的状态,我们是可以直接得出的,这也是DP的边界*/ f[1][-w[1]+N]=1;/**/ for(int i=2;i<=n;++i) for(int j=10*n;j>=0;--j)/*类似有01背包*/ { if(j+w[i]>=0&&j+w[i]<=10*n)/*注意要下越界和上越界都判断,因为w[i]的正负是不一定的*/ f[i][j]=min(f[i][j],f[i-1][j+w[i]]+1);/*设t是前i-1个牌的某个翻法的差值推到f[i][j]这个状态,如果不翻牌,那么j=t+w[i],可以倒推出t的两个值,对应着翻牌与不翻牌*/ if(j-w[i]>=0&&j-w[i]<=10*n) f[i][j]=min(f[i][j],f[i-1][j-w[i]]); } if(f[n][5*n]<Q) printf("%d\n",f[n][5*n]);/*Q是我估计的最大翻转次数,这个用来判断当前的差值能不能通过翻牌得到,如果不能得到,一定比Q大,那么再向5*n的两侧找*/ else { for(int i=5*n-1,j=5*n+1;j<=10*n&&i>=1;++j,--i) { if((f[n][i]<Q||f[n][j]<Q)) { printf("%d\n",min(f[n][i],f[n][j])); return ; } } } } int main() { input(); DP(); return 0; }
分析:
能否使用滚动数组,进行压缩空间? 答案是否定的。 让我们仔细看一下这个DP方程: if(j+w[i]>=0&&j+w[i]<=10*n) f[i][j]=min(f[i][j],f[i-1][j+w[i]]+1); if(j-w[i]>=0&&j-w[i]<=10*n) f[i][j]=min(f[i][j],f[i-1][j-w[i]]); 它的确符合只与上一层有关,但是遇上一层哪一个有关,就与01背包不同了,因为01背包倒序循环,
更新只与比二维j小的数有关,但是这个方程明显也可能与比二维j大的数有关,
所以不能用。