题解 P1282 【多米诺骨牌】

皎月半洒花

2018-07-20 18:58:29

Solution

看到没有滚动数组的题解……哼,为什么没有卡空间呢qwq!? 首先我们看这个题,是个$dp$,线性单项递推第一维好像并没有什么问题。那我们不妨设$dp[i][j]$表示前$i$个物品,上下差值总和为$j$的最小交换次数。那么有一个显然的$init$是 ```cpp #include <cstdio> #include <cstring> #include <iostream> using namespace std ; const int MAXN = 1010, NN = 5050 ; int out ; int i, j, k, N, A[MAXN], B[MAXN], dp[MAXN][NN << 1] ; int main(){ cin >> N ; memset(dp, 0x7f, sizeof(dp)) ; dp[0][NN] = 0 ; ........... } ``` 然后我们思考状态转移方程$$dp_{i, j} = min\{dp_{i, j - a_i+b_i}, dp_{i,j+a_i-b_i}+1 \}$$ 类似于一个背包吧,那其实就是由翻或不翻转移过来的而已。诶,还有一点,由于是差值嘛,那么$A[i]-B[i]$在前者还是在后者,都是一样的,所以另一个方程当然也可以啊。$$dp_{i, j} = min\{dp_{i, j + a_i-b_i}, dp_{i,j-a_i+b_i}+1 \}$$ 但是我们考虑,其实第一维是没有必要的,因为我们最终的状态是确定的即$dp[n][$ 不确定 $]$,所以我们可以滚去一维。那么最终的核心代码就长这个样: ```cpp cin >> N ; memset(dp, 0x7f, sizeof(dp)) ; dp[0][NN] = 0 ; for(i = 1; i <= N; i ++) scanf("%d%d", &A[i], &B[i]) ; for(k = i = 1; i <= N; i ++, k ^= 1){ memset(dp[k], 0x7f, sizeof(dp[k])) ; for(j = -5000; j <= 5000; j ++) dp[k][j + NN] = min(dp[k ^ 1][j + A[i] - B[i] + NN], dp[k ^ 1][j - A[i] + B[i] + NN] + 1) ; ; } ``` 咳咳,不知为什么迷恋上了压行$emmm$。 那么,由于我们的第二维$j$是单调的,所以我们从前往后扫一遍,遇到合法的就输出即可(合法:$\leq1000$,因为题目中至多交换$1000$次) ```cpp for(i = 0; i <= 5000; i ++){ out = min(dp[N & 1][i + NN], dp[N & 1][-i + NN]) ; if(out <= 1000) { cout << out ; break ;} } ``` 嗯,最后贴一波标称吧$qwq$ ```cpp #include <cstdio> #include <cstring> #include <iostream> using namespace std ; const int MAXN = 1010, NN = 5050 ; int out ; int i, j, k, N, A[MAXN], B[MAXN], dp[2][NN << 2] ; int main(){ cin >> N ; memset(dp, 0x7f, sizeof(dp)) ; dp[0][NN] = 0 ; for(i = 1; i <= N; i ++) scanf("%d%d", &A[i], &B[i]) ; for(k = i = 1; i <= N; i ++, k ^= 1){ memset(dp[k], 0x7f, sizeof(dp[k])) ; for(j = -5000; j <= 5000; j ++) dp[k][j + NN] = min(dp[k ^ 1][j + A[i] - B[i] + NN], dp[k ^ 1][j - A[i] + B[i] + NN] + 1) ; ; } for(i = 0; i <= 5000; i ++){ out = min(dp[N & 1][i + NN], dp[N & 1][-i + NN]) ; if(out <= 1000) { cout << out ; break ;} } return 0 ; } ``` ~~你甚至可以把它压到15行(亲测)~~