The dynamic programming of state compression based on Unicom is a typical dynamic programming problem of state compression, because the nature of compression is not the same as the normal state compression dynamic programming 0 or one means unused, using two states, but the use of numbers to denote a similar plug state, so it is called the plug DP.
Plug DP is essentially a class of state compression DP, so it is still not able to avoid its exponential level of algorithmic complexity, even so, it is still much faster than the normal search algorithm.
"Example" Postal Vans (Usaco training 6.1.1)
There is a 4*n matrix, starting from the upper left corner, each time you can go to four directions, seeking to pass through each lattice exactly once, and then back to the beginning of the number of steps.
"Algorithmic Analysis"
See this problem, many readers think 4 is very small, will think of search algorithm or recursive formula, and in fact, the search algorithm can not solve this problem, when n slightly larger, search algorithm even if the writing is beautiful, can not pass this question. The subject does have a recursive formula, but the recursive formula is not so easy to find, so you can consider using the plug DP.
In order to better understand the plug DP, first introduce the following several concepts:
1. Plug
For any of the lattice points on the matrix, the path will always pass through it, that is, to enter from one end, out of the way, there are 6 kinds of cases, as follows:
One of the prerequisites for a legitimate path to be fulfilled is that the path plugs on each of its squares are one of the six and match each other. Matching means that if a grid above the grid has a downward plug, then the grid must have an upward plug to match it.
2. Contour Lines
For any non-decision-making lattice, only its top and left grids have an effect on its placement method, so a contour line can be drawn based on the current decision-making grid, separating the lattice that has been made and the decision is not.
As is the two typical contour lines, one is based on the lattice contour line, the current is to transfer the contour line corner of the lattice, one is based on the line of the contour line, which is currently transferred below the contour line of an entire line.
For the first case, the plug involved in a total of n+1, where n under the plug, 1 right plug, the number of plugs to be saved is n+1, for the second case, only n the lower plug, the number of plugs to be saved is N.
3. Connectivity
For such dynamic programming problems, the connectivity of these plugs needs to be recorded in addition to each plug being saved. For example, using [(3,4)] to indicate that the 1th, 2 lattice of the line has been connected, and that the 3 and 4 lattices are connected.
, the lower plug is exactly the same, but the connectivity is completely different. Therefore, it is also necessary to indicate their connectivity in the state.
Because the plug representation is already an exponential level of space, indicating connectivity if you need an exponential space, then the space and time consumption will be huge! Therefore, there is a need for better ways to express connectivity, a common approach that we call "parenthesis notation".
For the four squares of the same row, assuming they all have a plug, then their connectivity may only have two conditions [(3,4)],[(1,4), (2,3)], and not possibly [(1,3), (2,4)], more generally, because the plug is never possible to cross, so There is no crossover between any of the two grids. This is exactly the same as the brace match!
The basic idea of bracket notation is the three-way system:
0: No plug status, denoted by #
1: Opening parenthesis plug, with (indicating
2: Right parenthesis plug, used) to indicate
The left (using a grid shift) can be expressed as: (() #)
Graph right (using row to shift) can be expressed as: (())
On this basis, continue to understand the plug DP mode of state transfer.
1. State transfer based on the lattice point:
The mode of state transfer based on lattice points each time the transfer is only one lattice, it is necessary to consider whether there is a plug on the left side of the lattice and above.
no plug on the left, no plug above. [****##****] (* represents any of the # ()), can only be added here a plug, the status changes to [* * ** * * **].
There is only one plug on the left and above. At this point, the lattice must have a Plug and this plug to match, the other plug into the bottom or right, this lattice equivalent to continue the previous plug connected state, so the transfer method is: the position of the plug is not moving (plug into the lower) or move one (to starboard).
There is a plug on the left and a plug above. In this case, the equivalent of merging two connected blocks requires consideration of the parentheses between the two plugs:
Case1: "((", both are left plugs, at this point to put the two two connected components together , you must modify their corresponding closing parenthesis, so that their corresponding right parenthesis match, for example: [# (#)], will be two ' (' Merge, You need to modify the second ' (' matched ') ' as ' (', so that they continue to match. Change to [## # ##()]
CASE2: ")", both are right plugs, at this point, merging them is equivalent to directly turn them into "# #".
CASE3: "()", that is, the two are matched, at this time, the merger they are equivalent to connect them together to form a circuit, for the need to form a circuit of the topic, only in the last lattice formation loop is the legal transfer mode.
Where there is a plug on the left, no plug on the top and no plug on the left, there is a plug above the situation is very similar, in the implementation of the code can be combined.
2. Row-based State transfer:
Based on the row-state transitions, use the search method to implement the legal status of each DFS row, and then update the status to the next line. It is easy to implement, but the algorithm is very complex and very few people use it. We will not dwell on it here.
It is important to note that although the plug DP uses a three-way state representation, it is often implemented with the use of a quad (using only 0, 1, 2,3 does not mean anything). The reason is that because the design of the computer itself leads to a very fast calculation of the power of 2, the use of the four-input system can be used to speed up the operation.
In addition, since in the process of state transfer, it is necessary to know the position of the closing parenthesis for each opening parenthesis, because the legal state is very limited, so the legal state and the position of the closing parenthesis matched by each opening parenthesis in these states can be recorded by preprocessing, and the time complexity is reduced by using the extra space.
when implementing the code, use an int type to hold each state, and the parentheses in the quad from low to high indicate each left-to-right parenthesis, for example: 9 (+) 3 actually represents a left-to-right matching brace (), please note the reader when reading the code.
When the reading data is too large, it will exceed the long long type, so it is necessary to use the high-precision operation, which can be realized by the method of the structure-volume replication addition operator. Readers who are familiar with Java can also use the BigInteger class directly in Java.
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream>using namespace std;const int n = 4;struct num{short arr[500];int len;void init (int i) {memset (arr, 0, sizeof (arr)); Arr[0] = I;len = 1;} void print () {for (int i = len-1; I >= 0; i--) {cout << arr[i];} cout << Endl;}}; void operator + = (num &a, num b) {A.len = max (A.len, B.len); for (int i = 0; i < A.len; i++) {A.arr[i] + = B.arr[i];a . arr[i + 1] + = A.arr[i]/10;a.arr[i]%= 10;} if (A.arr[a.len]) a.len++;} int N;int stat[1110];int brk[1110][8], stack[8], top = 0, tot = 0; Num Dp[8][1110];int Main () {freopen ("vans.in", "R", stdin); Freopen ("Vans.out", "w", stdout); cin >> N;int m = 1 <& Lt ((n + 1) << 1); for (int i = 0; i < m; i++) {top = 0;for (int j = 0; J <= N; j + +) {int x = i >> (j <&L T 1) if ((x & 3) = = 1) stack[top++] = J;if ((x & 3) = = 2) if (top--) {brk[tot][j] = Stack[top];brk[tot][stack[top]] = J;} else Break;if ((x & 3) = = 3{top = -1;break;}} if (!top) stat[tot++] = i;} Num ans;ans.init (0); memset (DP, 0, sizeof (DP));DP [N][0].init (1); for (int k = 1; k <= N; k++) {for (int i = 0; i < tot ; i++) {if (Stat[i] & 3) dp[0][stat[i]].init (0); else dp[0][stat[i]] = Dp[n][stat[i] >> 2];} for (int i = 1; I <= n; i++) {int x = (i-1) << 1;memset (Dp[i], 0, sizeof (Dp[i])); for (int j = 0; j < tot; j+ +) {int p = (Stat[j] >> x) & 3;int q = (Stat[j] >> (x + 2)) & 3;//#-A ()//9 = (21) 4//No plug on left !p &&!q) Dp[i][stat[j] | (9 << x)] + = dp[i-1][stat[j]];else//with plug if (p && q) {//two (or two)) if (p = = q) {//((...))//# # ... ()//5 = (11) 4: # # = ((^ 5//() =) ^ 3//two ((, match its position) to (if (p = = 1) dp[i][stat[j] ^ (5 << x) ^ (3 << (brk[ J][i] << 1)] + = dp[i-1][stat[j]];//((...))->//() ... ##//10 = (22) 4//two)), match the position (to) Else Dp[i][stat[j] ^ (1 0 << x) ^ (3 << (brk[j][i-1] << 1))] + = Dp[i-1][stat[j]];} Else() or) ({//), in case of the last lattice, add the answer, otherwise skip if (p = = 1) {if (k = = N && i = = n && stat[j] = = (9 << x)) ans + = Dp[i-1][stat[j]];} (The case, directly put) (change to # #else Dp[i][stat[j] ^ (6 << x)] + + dp[i-1][stat[j];//) (# #, 6 = (12) 4}}//only one of the locations has plug else {//When the original state is # (or #), the state does not change means the plug to the right//when the original state is (#或者) #时, the state does not change the plug down dp[i][stat[j]] + = dp[i-1][stat[j]];//When the original state is # (or #), The status switch means that the plug is down//when the original state is (#或者) #时, the status switch means the plug to the right Dp[i][stat[j] ^ (p << x) ^ (q << x + 2) | (P << x + 2) | (q << x)] + = Dp[i-1][stat[j]];}}} After connecting the answer is multiplied by 2, because a ring has two traversing directions of ans + = Ans;ans.print (); return 0;}
Change the subject slightly, you can have a lot of plug DP class title: For example, from (to) point, go back to (1,n) point, find a number of rings to cover all the points on the map, from (to) point, all the points go exactly once, but do not need to go back to the point. In essence, it's a little bit of a change, just to thoroughly understand the idea of plug DP, to make these questions is not a problem.
1. Go back to (1,n) point from (to) point. We can artificially think that this maze is entered from (0,1) point, and finally back to (0,n) point, so that we only need to transfer the state of the time, the mandatory requirements (and 1,n) point must have the plug, and other unchanged.
2. Locate a number of rings to cover all the points on the graph. In the original topic, there must be only one ring, so only at the last point (N,n), only to deal with the same time there is a left Plug and they are (), in this problem, only need to remove this special request, namely: At any point, as long as the top left has Plug and they are (), To merge them.
3. Start at (a) point, take all the points exactly once, but do not need to go back to the point. At this point, the man-made (to) points to add a plug, so that the (first) point can only be passed once, and then added to the state of a one-dimensional state [0 or 1] to indicate whether a point is currently used as the end point, when this dimension state is 0, You can only add a plug to a grid when it is transferred, then change the 0 to 1, and finally to the (N,N) point request must be () to update the answer; If the last node (n,n) is still 0, consider the interpolation of the two plugs and update the answer here.
Dynamic programming plug DP Getting Started