USACO 6.1 Postal Vans(一道神奇的dp)
Postal Vans
Tiring of their idyllic fields, the cows have moved to a new suburb. The suburb is a rectangular grid of streets with a post office at its Northwest corner. It has four avenues running East-West and N (1 <= N <= 1000) streets running North-South.
For example, the following diagram shows such a suburb with N=5 streets, with the avenues depicted as horizontal lines, and the post office as a dark blob at the top-left corner:
Each day the postal van leaves the post office, drives around the suburb and returns to the post office, passing exactly once through every intersection (including those on borders or corners). The executives from the post company want to know how many distinct routes can be established for the postal van (of course, the route direction is significant in this count).
For example, the following diagrams show two such routes for the above suburb:
As another example, the following diagrams show all the four possible routes for a suburb with N=3 streets:
Write a program that will determine the number of such distinct routes given the number of streets.
PROGRAM NAME: vans
INPUT FORMAT
- Line 1: A single integer, N
SAMPLE INPUT (file vans.in)
4
OUTPUT FORMAT
- Line 1: A single integer that tells how many possible distinct routes corresponding to the number of streets given in the input.
SAMPLE OUTPUT (file vans.out)
12
————————————————————————————————题解
又到了看一大面的英文Analysis然后翻译的时候了
先说说网上的做法【毕竟想看Analysis还得把题给过掉而且我不会做这道题】
这个看着还是比较亲切的,比什么插头dp要好多了【我也不会啊】
附个我的代码
1 /* 2 ID: ivorysi 3 LANG: C++ 4 PROG: vans 5 */ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <queue> 10 #include <set> 11 #include <vector> 12 #define siji(i,x,y) for(int i=(x);i<=(y);++i) 13 #define gongzi(j,x,y) for(int j=(x);j>=(y);--j) 14 #define xiaosiji(i,x,y) for(int i=(x);i<(y);++i) 15 #define sigongzi(j,x,y) for(int j=(x);j>(y);--j) 16 #define inf 0x5f5f5f5f 17 #define ivorysi 18 #define mo 97797977 19 #define hash 974711 20 #define base 47 21 #define fi first 22 #define se second 23 #define pii pair<int,int> 24 #define esp 1e-8 25 typedef long long ll; 26 using namespace std; 27 struct bignum { 28 vector<int> s; 29 30 bignum operator =(string x) { 31 s.clear(); 32 gongzi(i,x.length()-1,0) { 33 s.push_back(x[i]-'0'); 34 } 35 return *this; 36 } 37 bignum operator =(long long x) { 38 s.clear(); 39 do { 40 s.push_back(x%10); 41 x/=10; 42 }while(x>0); 43 return *this; 44 } 45 bignum(string x) { 46 *this=x; 47 } 48 bignum(long long x=0LL) { 49 *this=x; 50 } 51 bignum operator + (const bignum b) const { 52 bignum c; 53 c.s.clear(); 54 for(int g=0,k=0;;++k) { 55 if(g==0 && k>=b.s.size() && k>=s.size()) {break;} 56 int x=g; 57 if(k<b.s.size()) x+=b.s[k]; 58 if(k<s.size()) x+=s[k]; 59 c.s.push_back(x%10); 60 g=x/10; 61 } 62 return c; 63 } 64 bignum operator - (const bignum b) const { 65 bignum c; 66 c.s.clear(); 67 for(int g=0,k=0;;++k) { 68 if(g==0 && k>=s.size()) {break;} 69 int x=s[k]-g; 70 if(k<b.s.size()) x-=b.s[k]; 71 if(x<0) {g=1;x+=10;} 72 else g=0; 73 c.s.push_back(x); 74 } 75 return c; 76 } 77 bignum operator * (const bignum b) const { 78 bignum c; 79 int g=0,x; 80 c.s.clear(); 81 siji(i,1,s.size()+b.s.size()+2) c.s.push_back(0); 82 for(int i=0;i<s.size();++i) { 83 for(int j=0;j<b.s.size();++j) { 84 x=s[i]*b.s[j]+g+c.s[i+j]; 85 c.s[i+j]=x%10; 86 g=x/10; 87 } 88 while(g!=0) {c.s[i+b.s.size()]=g;g=0;} 89 } 90 while(c.s[c.s.size()-1]==0) c.s.pop_back(); 91 return c;//没打返回值…… 92 } 93 bignum &operator +=(const bignum b) { 94 *this=*this+b; 95 return *this; 96 } 97 bignum &operator -=(const bignum b) { 98 *this=*this-b; 99 return *this; 100 } 101 bignum &operator *=(const bignum b) { 102 *this=*this*b; 103 return *this; 104 } 105 friend ostream& operator << (ostream &out, const bignum& x) { 106 for(int i=x.s.size()-1;i>=0;--i) { 107 out<<x.s[i]; 108 } 109 return out; 110 } 111 friend istream& operator >> (istream &in,bignum &x) { 112 string str; 113 if(!(in>>str)) return in; 114 x=str; 115 return in; 116 } 117 }f[1005],g[1005]; 118 int n; 119 void solve() { 120 scanf("%d",&n); 121 g[1]=2;g[2]=2;g[3]=8;f[1]=0;f[2]=2;f[3]=4; 122 siji(i,4,n) { 123 g[i]=f[i-1]*bignum(2)+g[i-1]+g[i-2]-g[i-3]; 124 f[i]=f[i-1]+g[i-1]; 125 } 126 cout<<f[n]<<endl; 127 } 128 int main(int argc, char const *argv[]) 129 { 130 #ifdef ivorysi 131 freopen("vans.in","r",stdin); 132 freopen("vans.out","w",stdout); 133 #else 134 //freopen("f1.in","r",stdin); 135 #endif 136 solve(); 137 return 0; 138 }
于是乎,抱着一种鬼迷心窍的态度看了看USACO这道题的Analysis
其实思路很简单,就是我英文不好理解起来费了不少时间,于是我又决定当翻译了【nocow似乎有个类似的做法】
Alex Schwendner
This is a DP problem. The key to any dynamic programming problem is, of course, finding the subproblems. In this case, one noteworthy aspect of the problem is that while the grid may have up to 1000 vertical streets, it has only 4 horizontal streets. Consider a vertical cross-section cutting through the four horizontal street (and not intersecting a vertical street). There are only a few different possible ways in which our route could use these streets. Thus, we let the "state" for our DP be the distance from one end (say the right end) of the grid and the configuration of these four streets.
这是一道DP问题。当然,任何DP问题的关键是找到子问题。【真是至理名言……】在这个情况中,题中一个值得注意的方面是这个网格也许至多会有1000条垂直的街道,然而只有4条水平的街道。考虑一个垂直的横截面切过4个水平的街道(并且不与一个垂直的街道交叉)。这样我们的路径使用这些街道只会有几个不同的路径。因此,我们让DP方程的状态是和网格中一个结束点的距离(我们说的是右下角的结束点)和我们使用四个街道的形式。
【那么我解释一下“一个垂直的横截面切过4个水平的街道(并且不与一个垂直的街道交叉)”这句话】
也就是绿色那部分位于两个垂直街道之间这就是横截面,这个横截面有四个小横条(蓝色部分),下面就是对这蓝色小横条使用进行讨论
What are the possible configurations? Note that because we must connect whichever streets we use into a cycle, our path must traverse an even number of the four streets (i.e., either 2 or 4). Thus, the configuration is one of the 8 following patterns (the last two of which appear similar; read on):
- - |
- - |
- - |
- - |
- - |
- - |
- - - - |
- - - - |
哪些是可能的形式呢?注意到因为我们在这一个圈中无论使用哪个街道,我们都要经过四个街道中的偶数个(换句话,不是2就是4)【也就是我们使用不是2就是4个蓝色小横条】。因此,这个形式只能是以下八种形状的一种(最后两种出现的形状是相同的,看下面的表格)
The first 6 patterns with two lines represent all configurations in which two streets are used in the van route. The last two patterns both represent all four streets being used in the van route, but differ in the connection of the paths. In #7, the top two paths are connected and the bottom two paths are connected by the part of the van route to the right. In #8, the top and bottom paths are connected, and the middle two paths are connected by the rest of the van route (to the right).
前六个用两条横线的形状代表卡车路线用两个街道的所有形式。最后两个形状代表四个街道在卡车路线中都被使用,但是他们不同在路径的连接,在7的情况里,顶端两个路径是相连的,底端两个两个路径是相连的,通过一部分卡车去向右边的路径。在8的情况里,最顶端和最底端的路径是相连的,中间的路径通过卡车剩余的路径相连(到右边的)。
这句话理解起来有点费力,但是其实换一种说法很好理解
7:水平向左凸起一块
8:水平向右凸起一块
因为dp是不断从右向左推进,向左凸起时上两个和下两个已经通过右边的相连了,而向右凸起上下两条边已经通过最右边的边相连了,中间的还没连上
So how do we go from one state to another? To go one intersection farther to the left, we must visit all four intersections of one vertical street with the four hourzontial streets. This means, for instance, that we cannot connect two Type 1 patterns, because then we would not be visiting the bottom two intersections. Also, we cannot connect a Type 1 pattern with a Type 6 pattern, because both parts of the path would have to traverse the same section of the vertical street, and we can't visit an intersection twice. On the other hand, we could connect a Type 1 pattern with a Type 3 pattern. Lastly, we cannot connect the Type 7 and Type 8 patterns (with all four streets used) in such a way as to disconnect the route. Putting this all togeather, we have the following transition matrix:
0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 1 0 1 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 1 0 0 0 0 1
那么我们怎么转移呢?去左边一个更远的交叉点,我们必须经过一个垂直街道穿过四个水平街道所有四个交叉点,这意味着,举个例子,我们不能把两个形状1连在一起,因为我们不能不经过两个底端的交叉点。同样,我们不能把形状1和形状6连在一起,因为这两个部分会不得不穿过一个垂直街道的相同部分,而且不能不能经过同一个交叉点两次。再者来说,我们可以把一个形状1和一个形状3连在一起。最后,我们不能连接形状7和形状8(所有4个街道都被用了)使得在一条不连通的路上。把这些都考虑了,我们有了如下的转移矩阵。【还没感受过把转移方法写成矩阵的却又没有快速幂的方式】
Above, we have a 1 in the i-th row and j-th column if we can transition to a Type i pattern from a Type j pattern. Thus, we know how to go from state to state: to calculate the numbers of routes with the 8 patterns on the left edge at a certain distance X from the right edge of the grid, apply the matrix to the numbers of routes with the 8 patterns on the left edge at distance X-1. The matrix is asymetric because patterns 7 and 8 differ in how the paths are connected to the right. Thus transitioning from a Type 7 or 8 pattern is not the same as transitioning to a Type 7 or 8 pattern.
以上,我们有一个1在i行j列表示我们可以从j状态转移到i状态。因此,我们知道怎么在状态与状态之间转移:用这个矩阵从距离右边X-1到距离右边X。【那一长句意会就好】这个矩阵是不对称的因为形状7和8不同在路径怎样连接到右边。因此从7,8转移出来和转移到7,8是不同的。
We just need initial and final conditions. To start (on the right), we can have either a Type 3 pattern or a Type 7 pattern. We need to cover all 4 intersections, so it has to use both the top and bottom streets, but we can't connect the paths in the right way for a Type 8 pattern without more room. For a Type 7 we just have two backwards "C" shaped pieces. At the end, when we get to the left side of the grid, to get our answer, we add up the number of ways to end with a Type 3 pattern or a Type 8 pattern. (Type 8 this time because we don't have room to reconnect a Type 7, but for a Type 8, we just connect the top two streets to each other and the bottom two to each other, with two "C" shaped pieces.) We then multiply by two, because we can traverse the route in two directions.
我们只需要最初和最终的状态,开始(从右)我们需要一个3或者一个7形状。我们需要覆盖所有四个交叉点,所以我们需要同时使用顶端和底端的街道,但是我们不能连接路径在右边没有任何空间的时候使用形状8,【对于形状7我们只需要两个倒C字。】(←意会!!我翻译不好!!)最后当我到方格左边的时候,为了得到我们的答案,我们加上到形状3或者形状8的值(这个时候用形状8因为我们没有空间去再连一个形状7,但是对于形状8我们只有连接两个顶端的街道并且两个底端相连,我们只需要两个正C字)我们然后乘以2,因为我们可以从两个方向走路径
这道题代码把矩阵转了圈,所以我重写一份
1 /* 2 ID: ivorysi 3 LANG: C++ 4 PROG: vans 5 */ 6 #include <iostream> 7 #include <cstdio> 8 #include <cstring> 9 #include <queue> 10 #include <set> 11 #include <vector> 12 #define siji(i,x,y) for(int i=(x);i<=(y);++i) 13 #define gongzi(j,x,y) for(int j=(x);j>=(y);--j) 14 #define xiaosiji(i,x,y) for(int i=(x);i<(y);++i) 15 #define sigongzi(j,x,y) for(int j=(x);j>(y);--j) 16 #define inf 0x5f5f5f5f 17 #define ivorysi 18 #define mo 97797977 19 #define hash 974711 20 #define base 47 21 #define fi first 22 #define se second 23 #define pii pair<int,int> 24 #define esp 1e-8 25 typedef long long ll; 26 using namespace std; 27 struct bignum { 28 vector<int> s; 29 30 bignum operator =(string x) { 31 s.clear(); 32 gongzi(i,x.length()-1,0) { 33 s.push_back(x[i]-'0'); 34 } 35 return *this; 36 } 37 bignum operator =(long long x) { 38 s.clear(); 39 do { 40 s.push_back(x%10); 41 x/=10; 42 }while(x>0); 43 return *this; 44 } 45 bignum(string x) { 46 *this=x; 47 } 48 bignum(long long x=0LL) { 49 *this=x; 50 } 51 bignum operator + (const bignum b) const { 52 bignum c; 53 c.s.clear(); 54 for(int g=0,k=0;;++k) { 55 if(g==0 && k>=b.s.size() && k>=s.size()) {break;} 56 int x=g; 57 if(k<b.s.size()) x+=b.s[k]; 58 if(k<s.size()) x+=s[k]; 59 c.s.push_back(x%10); 60 g=x/10; 61 } 62 return c; 63 } 64 bignum operator - (const bignum b) const { 65 bignum c; 66 c.s.clear(); 67 for(int g=0,k=0;;++k) { 68 if(g==0 && k>=s.size()) {break;} 69 int x=s[k]-g; 70 if(k<b.s.size()) x-=b.s[k]; 71 if(x<0) {g=1;x+=10;} 72 else g=0; 73 c.s.push_back(x); 74 } 75 return c; 76 } 77 bignum operator * (const bignum b) const { 78 bignum c; 79 int g=0,x; 80 c.s.clear(); 81 siji(i,1,s.size()+b.s.size()+2) c.s.push_back(0); 82 for(int i=0;i<s.size();++i) { 83 for(int j=0;j<b.s.size();++j) { 84 x=s[i]*b.s[j]+g+c.s[i+j]; 85 c.s[i+j]=x%10; 86 g=x/10; 87 } 88 while(g!=0) {c.s[i+b.s.size()]=g;g=0;} 89 } 90 while(c.s[c.s.size()-1]==0) c.s.pop_back(); 91 return c;//没打返回值…… 92 } 93 bignum &operator +=(const bignum b) { 94 *this=*this+b; 95 return *this; 96 } 97 bignum &operator -=(const bignum b) { 98 *this=*this-b; 99 return *this; 100 } 101 bignum &operator *=(const bignum b) { 102 *this=*this*b; 103 return *this; 104 } 105 friend ostream& operator << (ostream &out, const bignum& x) { 106 for(int i=x.s.size()-1;i>=0;--i) { 107 out<<x.s[i]; 108 } 109 return out; 110 } 111 friend istream& operator >> (istream &in,bignum &x) { 112 string str; 113 if(!(in>>str)) return in; 114 x=str; 115 return in; 116 } 117 }f[2][10],ans; 118 int n; 119 int now,to; 120 bool martix[9][9]= { 121 {}, 122 {0,0,0,1,0,0,0,0,1}, 123 {0,0,0,0,0,1,0,0,1}, 124 {0,1,0,0,1,0,1,1,0}, 125 {0,0,0,1,0,0,0,0,0}, 126 {0,0,1,0,0,0,0,0,0}, 127 {0,0,0,1,0,0,0,0,1}, 128 {0,1,0,0,0,0,1,1,0}, 129 {0,0,0,1,0,0,0,0,1} 130 }; 131 void solve() { 132 scanf("%d",&n); 133 now=0;to=1; 134 f[now][3]=1;f[now][7]=1; 135 siji(k,2,n-1) {//因为有n个垂直街道横截面只有n-1个 136 siji(i,1,8) { 137 f[to][i]=0; 138 } 139 siji(i,1,8) { 140 siji(j,1,8) { 141 if(martix[i][j]) { 142 f[to][i]=f[to][i]+f[now][j]; 143 } 144 } 145 } 146 siji(i,1,8) { 147 f[now][i]=f[to][i]; 148 } 149 now=now^1,to=to^1; 150 } 151 ans=f[now][3]*2+f[now][8]*2; 152 if(n==1) ans=0; 153 cout<<ans<<endl; 154 } 155 int main(int argc, char const *argv[]) 156 { 157 #ifdef ivorysi 158 freopen("vans.in","r",stdin); 159 freopen("vans.out","w",stdout); 160 #else 161 //freopen("f1.in","r",stdin); 162 #endif 163 solve(); 164 return 0; 165 }