哈希链表
众所周知,哈希是有冲突的可能性的,而且在状态数越多,冲突的概率就越高。目前掌握的处理方案有多哈希,但仍有冲突的可能;
STL
\text{STL}
STL 直接整个记录下来,自带大常数和
log
\text{log}
log。 插头 DP 都不用,而是采取了哈希链表的方法。
具体而言就是将轮廓线上插头状态按一定的规则哈希成一个整数后,甩到其对应的链上。
形象地说,就是开了一定的哈希桶,然后哈希后的整数取模桶数,得到一个桶的编号,接着把这个轮廓线相应信息装成结构体,放到这个桶里。显然这个桶里很有可能有其余的轮廓线状态,那么就直接接在最后一个的后面。用前向星,数组来做。应该不会有人要刚vector吧
桶数随意,但肯定是越大且是个质数更好,因为后面定位轮廓线哈希位置的时候,是要从其所在桶里面开始一个一个遍历比较的,如果一个桶内太多轮廓线状态,遍历时间复杂度就有点要命了。
#define mod 299989
struct HashTable { int sta[2], dp[2], nxt; }Hash[300000];
void insert( int sta, int val ) {
int key = sta % mod;
for( int i = head[key];i;i = Hash[i].nxt )
if( sta == Hash[i].sta[now] ) { Hash[i].dp[now] += val; return; }
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = val;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
};
插头DP
概念
插头DP ,是一类基于连通性的状态压缩动态规划,用状压DP来处理联通问题。本质就是状压。
常见的联通问题:多回路问题、路径问题、简单回路问题、广义路径问题、生成树问题。
插头DP 一般是 逐格转移 的,少有逐行转移。
而逐格转移就是将格子划分成已转移的格子和未转移的格子,我们将起到这样分类作用的工具叫做 轮廓线。
如图,红色线就是此时的轮廓线,当前在处理的格子是黄色格子,也就是说当前处理的格子也被划分到未转移的格子集合中。
不难发现,
m
m
m 列却有
m
+
1
m+1
m+1 个插头 / 轮廓线。
既然叫做 插头DP,那么什么又是插头呢?插头就是一个格子上下左右四个方向的插头,如图。
前面提到了主要是用来解决连通性问题的,一个格子与其余格子联通,怎么联通的
这个插头可以帮助我们知道两个相邻格子是怎样连接的,比如这个格子是和上面格子联通的,那么就是一条上下边,是通过这个格子的上插头完成的。还有如果这个格子恰好是联通的拐点,就是通过相邻插头,比如从左插头联通这个格子又从下插头连出去。
一个格子有四个插头,一个存在的插头表示 在它代表的方向上 能与相邻的格子连接(联通)。不存在就不能。
要求一个回路,也就意味着最后所有的非障碍格子通过插头连接成了一个连通块,那么久需要转移时记录格子的连通情况。
我们递推的时候就是依据轮廓线上插头的存在性,求出所有能转移到的合法状态。
形象地理解,每个格子可以看作一块块拼图,插头就是两块拼图之间的衔接。
在逐格转移中一般只记录轮廓线上的插头存在性,因为这些才与后面的格子是有可能直接相连的。
转移到新格子后,轮廓线也会相应的移动。如图。不难发现,同行内的移动只跟当前处理格子的左插头和上插头有关,并且是转移到自己的右插头和下插头。如果换行,发现唯一一个竖着的特殊插头是不会有的。但不影响,后面会提到。 所以当前格子合法状态至于上一次处理的格子有关,这里就可以采用滚动数组优化了,减少空间花费。
一般是设
d
p
[
i
]
[
j
]
[
s
t
a
]
:
(
i
,
j
)
dp[i][j][sta]:(i,j)
dp[i][j][sta]:(i,j) 位置时状态是
s
t
a
sta
sta 的方案数 / 代价和 等等。状压的就是轮廓线状态。
括号表示法 / 最小表示法
记录当前的联通状态 / 轮廓线上的插头状态,准确地讲是记录轮廓线上的状态,一般有两种方法。
括号表示法
当前已经联通的轮廓线上的插头,靠左边的是左括号,靠右边的是右括号。没有插头就另设字符。
轮廓线上从左到右
a
,
b
,
c
,
d
a,b,c,d
a,b,c,d 插头,如果
a
,
c
a,c
a,c 连通,并且与
b
b
b 不连通,那么
b
,
d
b,d
b,d 一定不连通。这个性质对所有的棋盘模型的问题都适用。
感性理解这很显然。
从左到右的排序是从左边的第一条轮廓线走到右边第一条轮廓线依次经历轮廓线的顺序。
最小表示法
同样地,从左到右的顺序,找到第一个不是障碍的轮廓线插头,然后找到跟这个插头联通的其余轮廓线插头,标记为第一个连通块,然后找第二个不是障碍且没有标记的插头,以此类推。
最小表示法比较好写,但括号表示法似乎时间更优秀。括号表示法是要每种情况分类讨论的,回路到还好只有三种状态,只用讨论九种,要是简单路径就会涉及到单独插头的问题,就是讨论十六种了。写挂的概率直线飙升。
最小表示法是有些情况可以合并成一个写。
且括号表示法的状态修改较为麻烦,都不一样,所以无法合并情况,而最小表示法比较暴力直接解码重编。
竖着的那条轮廓线比较特殊,所以我一般把它放到第
0
0
0 位。
例题
由于涉及到哈希压缩,一般都是用位运算,所以有些题目虽然是列的上限是
5
5
5 这种,我们也习惯开成
8
8
8 位的,这在最小表示法中很常见。
当你题目做多了过后,就会发现基本上都是一样的框架的感觉。
洛谷插头dp板题
luogu P5056 【模板】插头dp
此题就用括号表示法来做吧。
-
新建一个连通块 这个新建是就当前已操作过的格子而言的,但本题肯定是最后会联通成一个的,否则就不合法了。 能新建联通块当且仅当这个格子的上边轮廓线和左边轮廓线都没有插头。如图。 -
合并两个联通块 那么肯定是两个轮廓线都指了插头,这里必须做合并操作,不然就无法形成回路。
- 合并的是两个不同连通块的右括号,那么就要找两个右括号相对应的左括号更靠右的一个,变成右括号。
- 合并的是两个不同连通块的左括号,那么就要找两个左括号相对应的右括号更靠左的一个,变成左括号。
-
延续之前的联通情况。
-
不拐弯。 不拐弯最简单,不用改轮廓线情况,直接插入就行。 注意虽然下面这张图的括号表示法看似发生了改变,但是前面我提过,我喜欢把竖着个特殊轮廓线放在第
0
0
0 位,所以是没有改变的。 具体可见代码。 有的人就喜欢在
(
i
,
j
)
(i,j)
(i,j) 时,
j
j
j 代表格子横着的轮廓线,
j
?
1
j-1
j?1 代表特殊的竖轮廓线。那可能就需要修改?? -
拐弯的。 拐弯的其实也简单,就是直接延续。虽然括号表示法看起来没有变化,但实际上是有轮廓线改变的。 具体可见代码。
#include <bits/stdc++.h>
using namespace std;
#define mod 298999
#define int long long
int n, m;
int lst, now;
int head[300000], cnt[2];
bool mp[15][15];
struct HashTable { int sta[2], dp[2], nxt; }Hash[300000];
void insert( int sta, int val ) {
int key = sta % mod;
for( int i = head[key];i;i = Hash[i].nxt )
if( sta == Hash[i].sta[now] ) { Hash[i].dp[now] += val; return; }
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = val;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
};
struct node { int s[15]; };
node unZip( int sta ) {
node code;
code.s[0] = sta & 3;
for( int i = 1;i <= m;i ++ ) code.s[i] = ( sta >> ( i << 1 ) ) & 3;
return code;
}
int Zip( node code ) {
int sta = 0;
for( int i = 1;i <= m;i ++ ) sta |= ( code.s[i] << ( i << 1 ) );
sta |= code.s[0];
return sta;
}
signed main() {
int ans = 0, Endx, Endy;
scanf( "%lld %lld", &n, &m ); char ch[20];
for( int i = 1;i <= n;i ++ ) {
scanf( "%s", ch + 1 );
for( int j = 1;j <= m;j ++ )
if( ch[j] == '.' ) mp[i][j] = 1, Endx = i, Endy = j;
}
insert( 0, 1 );
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= m;j ++ ) {
lst = now, now ^= 1, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
for( int k = 1;k <= cnt[lst];k ++ ) {
node code = unZip( Hash[k].sta[lst] );
int Left = code.s[0], Up = code.s[j];
int dp = Hash[k].dp[lst];
if( ! mp[i][j] ) {
if( ! Left and ! Up ) insert( Zip( code ), dp );
}
else if( ! Left and ! Up ) {
if( mp[i + 1][j] and mp[i][j + 1] ) {
code.s[0] = 2, code.s[j] = 1;
insert( Zip( code ), dp );
}
}
else if( ! Left and Up ) {
if( mp[i + 1][j] ) insert( Zip( code ), dp );
if( mp[i][j + 1] ) {
code.s[0] = Up, code.s[j] = 0;
insert( Zip( code ), dp );
}
}
else if( Left and ! Up ) {
if( mp[i][j + 1] ) insert( Zip( code ), dp );
if( mp[i + 1][j] ) {
code.s[0] = 0, code.s[j] = Left;
insert( Zip( code ), dp );
}
}
else if( Left == 1 and Up == 1 ) {
int p, tot = 1;
for( p = j + 1;p <= m;p ++ ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 1;
insert( Zip( code ), dp );
}
else if( Left == 2 and Up == 2 ) {
int p, tot = -1;
for( p = j - 1;p;p -- ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 2;
insert( Zip( code ), dp );
}
else if( Left == 2 and Up == 1 ) {
code.s[0] = code.s[j] = 0;
insert( Zip( code ), dp );
}
else if( Left == 1 and Up == 2 ) {
code.s[0] = code.s[j] = 0; bool flag = 0;
for( int p = 0;p <= m;p ++ )
if( code.s[i] ) { flag = 1; break; }
if( ! flag and i == Endx and j == Endy ) ans += dp;
}
}
}
}
printf( "%lld\n", ans );
return 0;
}
CITY
BZOJ3125
如果板题看懂了,就会发现这道题只是有些限制需要判定而已。直接改改就能过。
#include <bits/stdc++.h>
using namespace std;
#define mod 298999
#define int long long
int n, m;
int lst, now;
int head[300000], cnt[2];
int mp[15][15];
struct HashTable { int sta[2], dp[2], nxt; }Hash[300000];
void insert( int sta, int val ) {
int key = sta % mod;
for( int i = head[key];i;i = Hash[i].nxt )
if( sta == Hash[i].sta[now] ) { Hash[i].dp[now] += val; return; }
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = val;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
};
struct node { int s[15]; };
node unZip( int sta ) {
node code;
code.s[0] = sta & 3;
for( int i = 1;i <= m;i ++ ) code.s[i] = ( sta >> ( i << 1 ) ) & 3;
return code;
}
int Zip( node code ) {
int sta = 0;
for( int i = 1;i <= m;i ++ ) sta |= ( code.s[i] << ( i << 1 ) );
sta |= code.s[0];
return sta;
}
signed main() {
int ans = 0, Endx, Endy;
scanf( "%lld %lld", &n, &m ); char ch[20];
for( int i = 1;i <= n;i ++ ) {
scanf( "%s", ch + 1 );
for( int j = 1;j <= m;j ++ ) {
if( ch[j] == '#' ) continue;
if( ch[j] == '.' ) mp[i][j] = 1;
if( ch[j] == '-' ) mp[i][j] = 2;
if( ch[j] == '|' ) mp[i][j] = 3;
Endx = i, Endy = j;
}
}
insert( 0, 1 );
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= m;j ++ ) {
lst = now, now ^= 1, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
for( int k = 1;k <= cnt[lst];k ++ ) {
node code = unZip( Hash[k].sta[lst] );
int Left = code.s[0], Up = code.s[j];
int dp = Hash[k].dp[lst];
if( ! mp[i][j] ) {
if( ! Left and ! Up ) insert( Zip( code ), dp );
}
else if( ! Left and ! Up ) {
if( mp[i][j] == 1 and mp[i + 1][j] and mp[i][j + 1] ) {
code.s[0] = 2, code.s[j] = 1;
insert( Zip( code ), dp );
}
}
else if( ! Left and Up ) {
if( ( mp[i][j] == 3 or mp[i][j] == 1 ) and mp[i + 1][j] )
insert( Zip( code ), dp );
if( mp[i][j] == 1 and mp[i][j + 1] ) {
code.s[0] = Up, code.s[j] = 0;
insert( Zip( code ), dp );
}
}
else if( Left and ! Up ) {
if( ( mp[i][j] == 1 or mp[i][j] == 2 ) and mp[i][j + 1] ) insert( Zip( code ), dp );
if( mp[i][j] == 1 and mp[i + 1][j] ) {
code.s[0] = 0, code.s[j] = Left;
insert( Zip( code ), dp );
}
}
else if( Left == 1 and Up == 1 and mp[i][j] == 1 ) {
int p, tot = 1;
for( p = j + 1;p <= m;p ++ ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 1;
insert( Zip( code ), dp );
}
else if( Left == 2 and Up == 2 and mp[i][j] == 1 ) {
int p, tot = -1;
for( p = j - 1;p;p -- ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 2;
insert( Zip( code ), dp );
}
else if( Left == 2 and Up == 1 and mp[i][j] == 1 ) {
code.s[0] = code.s[j] = 0;
insert( Zip( code ), dp );
}
else if( Left == 1 and Up == 2 and mp[i][j] == 1 ) {
code.s[0] = code.s[j] = 0; bool flag = 0;
for( int p = 0;p <= m;p ++ )
if( code.s[i] ) { flag = 1; break; }
if( ! flag and i == Endx and j == Endy ) ans += dp;
}
}
}
}
printf( "%lld\n", ans );
return 0;
}
ParkII
BZOJ2310
这道题就是求一条简单路径,那么就不要求一定是个回路,这个时候如果采取括号表示法讨论就有点多了。
最小表示法就较好写一点,多压一个路径的端点数量即可,最后的合法结果一定有且只有两个端点。也就是单独插头。
#include <bits/stdc++.h>
using namespace std;
#define mod 298999
#define ll long long
int n, m, num, lst, now;
int cnt[2], head[300000];
int v[105][10];
struct HashTable { int sta[2], nxt;ll dp[2]; }Hash[300000];
void insert( int sta, ll val ) {
int key = sta % mod;
for( int i = head[key];i;i = Hash[i].nxt )
if( Hash[i].sta[now] == sta ) {
Hash[i].dp[now] = max( val, Hash[i].dp[now] );
return;
}
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = val;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
}
struct node { int s[15]; };
int vis[10];
int Zip( node code ) {
int sta = 0, tot = 0;
for( int i = 1;i <= 6;i ++ ) vis[i] = 0;
for( int i = 0;i <= m;i ++, sta <<= 3 ) {
if( ! code.s[i] ) continue;
if( ! vis[code.s[i]] ) vis[code.s[i]] = ++ tot;
sta |= vis[code.s[i]];
}
sta |= num;
return sta;
}
node unZip( int sta ) {
node code;
num = sta & 7;
for( int i = m;~ i;i -- )
sta >>= 3, code.s[i] = sta & 7;
return code;
}
int main() {
scanf( "%d %d", &n, &m );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ )
scanf( "%d", &v[i][j] );
insert( 0, 0 );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ ) {
lst = now, now ^= 1, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
for( int k = 1;k <= cnt[lst];k ++ ) {
node code = unZip( Hash[k].sta[lst] );
ll dp = Hash[k].dp[lst] + v[i][j];
int left = code.s[0], up = code.s[j];
if( left and up ) {
if( left ^ up ) {
code.s[0] = code.s[j] = 0;
for( int p = 0;p <= m;p ++ )
if( code.s[p] == up ) code.s[p] = left;
insert( Zip( code ), dp );
}
}
else if( left or up ) {
int id = left + up;
if( i < n ) {
code.s[j] = id, code.s[0] = 0;
insert( Zip( code ), dp );
}
if( j < m ) {
code.s[0] = id, code.s[j] = 0;
insert( Zip( code ), dp );
}
if( num < 2 ) {
code.s[j] = code.s[0] = 0, num ++;
insert( Zip( code ), dp );
}
}
else {
insert( Zip( code ), Hash[k].dp[lst] );
if( i < n and j < m ) {
code.s[j] = code.s[0] = 6;
insert( Zip( code ), dp );
}
if( num < 2 ) {
num ++;
if( i < n ) {
code.s[j] = 6, code.s[0] = 0;
insert( Zip( code ), dp );
}
if( j < m ) {
code.s[j] = 0, code.s[0] = 6;
insert( Zip( code ), dp );
}
}
}
}
}
ll ans = -1e18;
for( int i = 1;i <= cnt[now];i ++ )
if( ( Hash[i].sta[now] & 7 ) == 2 ) ans = max( ans, Hash[i].dp[now] );
printf( "%lld\n", ans );
return 0;
}
Tony’s Tour
还要简单一点,稍微转化一下,在棋盘最后加入两行。
.####.
. . . . . .
就让起点和终点联通形成回路了,且这两行的走法之有一种。
#include <bits/stdc++.h>
using namespace std;
#define mod 298999
bool mp[15][15];
int head[300000], cnt[2];
int lst, now, n, m;
struct HashTable { int sta[2], dp[2], nxt; }Hash[300000];
void insert( int sta, int val ) {
int key = sta % mod;
for( int i = head[key];i;i = Hash[i].nxt )
if( Hash[i].sta[now] == sta ) {
Hash[i].dp[now] += val;
return;
}
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = val;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
}
struct node { int s[15]; };
int Zip( node code ) {
int sta = 0;
for( int i = 1;i <= m;i ++ ) sta |= ( code.s[i] << ( i << 1 ) );
sta |= code.s[0];
return sta;
}
node unZip( int sta ) {
node code;
code.s[0] = sta & 3;
for( int i = 1;i <= m;i ++ ) code.s[i] = ( sta >> ( i << 1 ) ) & 3;
return code;
}
int main() {
char ch[15];
while( scanf( "%d %d", &n, &m ) and n and m ) {
memset( mp, 0, sizeof( mp ) );
for( int i = 1;i <= n;i ++ ) {
scanf( "%s", ch + 1 );
for( int j = 1;j <= m;j ++ )
if( ch[j] == '.' ) mp[i][j] = 1;
else mp[i][j] = 0;
}
mp[n + 1][1] = mp[n + 1][m] = 1;
for( int i = 2;i < m;i ++ ) mp[n + 1][i] = 0;
for( int i = 1;i <= m;i ++ ) mp[n + 2][i] = 1;
n += 2;
now = 0, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
insert( 0, 1 );
int ans = 0;
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ ) {
lst = now, now ^= 1, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
for( int k = 1;k <= cnt[lst];k ++ ) {
node code = unZip( Hash[k].sta[lst] );
int left = code.s[0], up = code.s[j];
int dp = Hash[k].dp[lst];
if( ! mp[i][j] ) {
if( ! left and ! up ) insert( Zip( code ), dp );
}
else if( ! left and ! up ) {
if( mp[i + 1][j] and mp[i][j + 1] ) {
code.s[0] = 2, code.s[j] = 1;
insert( Zip( code ), dp );
}
}
else if( ! left and up ) {
if( mp[i + 1][j] ) insert( Zip( code ), dp );
if( mp[i][j + 1] ) {
code.s[0] = up, code.s[j] = 0;
insert( Zip( code ), dp );
}
}
else if( left and ! up ) {
if( mp[i][j + 1] ) insert( Zip( code ), dp );
if( mp[i + 1][j] ) {
code.s[j] = left, code.s[0] = 0;
insert( Zip( code ), dp );
}
}
else if( left == 2 and up == 1 ) {
code.s[0] = code.s[j] = 0;
insert( Zip( code ), dp );
}
else if( left == 1 and up == 1 ) {
int p, tot = 1;
for( p = j + 1;p <= m;p ++ ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 1;
insert( Zip( code ), dp );
}
else if( left == 2 and up == 2 ) {
int p, tot = -1;
for( p = j - 1;p;p -- ) {
if( code.s[p] == 1 ) tot ++;
if( code.s[p] == 2 ) tot --;
if( ! tot ) break;
}
code.s[0] = code.s[j] = 0, code.s[p] = 2;
insert( Zip( code ), dp );
}
else if( left == 1 and up == 2 ) {
code.s[0] = code.s[j] = 0; bool flag = 1;
for( int p = 0;p <= m;p ++ )
if( code.s[p] ) { flag = 0; break; }
if( flag and i == n and j == m ) ans += dp;
}
}
}
printf( "%d\n", ans );
}
return 0;
}
Efficient Tree
也是轮廓线
D
P
DP
DP 的题目,直接分类讨论就行。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 1000000007
struct node {
int ans, cnt;
node operator + ( node &now ) const {
if( ans < now.ans ) return *this;
else if( ans > now.ans ) return now;
else return { ans, ( cnt + now.cnt ) % mod };
}
node operator + ( int x ) { return { ans + x, cnt }; }
node operator * ( int x ) { return { ans, cnt * x % mod }; };
};
int L[1000][1000], U[1000][1000];
int lst, now, n, m;
int cnt[2], head[2500];
struct HASH { node dp[2]; int sta[2], nxt; }Hash[3000000];
void insert( int sta, node cost ) {
int key = sta % 2333;
for( int i = head[key];i;i = Hash[i].nxt )
if( Hash[i].sta[now] == sta ) {
Hash[i].dp[now] = Hash[i].dp[now] + cost;
return;
}
++ cnt[now];
Hash[cnt[now]].sta[now] = sta;
Hash[cnt[now]].dp[now] = cost;
Hash[cnt[now]].nxt = head[key];
head[key] = cnt[now];
}
int vis[10];
struct zip {
int s[15];
zip(){ memset( s, 0, sizeof( s ) ); }
};
int encode( zip code ) {
int sta = 0, tot = 0;
memset( vis, -1, sizeof( vis ) );
vis[0] = 0;
for( int i = 1;i <= m;i ++ ) {
if( vis[code.s[i]] == -1 ) vis[code.s[i]] = ++ tot;
sta = ( sta << 3 ) | vis[code.s[i]];
}
return sta;
}
zip decode( int sta ) {
zip code;
for( int i = m;i;i --, sta >>= 3 ) code.s[i] = sta & 7;
return code;
}
signed main() {
int T;
scanf( "%lld", &T );
for( int t = 1;t <= T; t++ ) {
scanf( "%lld %lld", &n, &m );
for( int i = 1;i <= n;i ++ )
for( int j = 2;j <= m;j ++ )
scanf( "%lld", &L[i][j] );
for( int i = 2;i <= n;i ++ )
for( int j = 1;j <= m;j ++ )
scanf( "%lld", &U[i][j] );
now = 0, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
insert( 0, { 0, 1 } );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ ) {
lst = now, now ^= 1, cnt[now] = 0;
memset( head, 0, sizeof( head ) );
for( int k = 1;k <= cnt[lst];k ++ ) {
zip code = decode( Hash[k].sta[lst] );
int left = code.s[j - 1], up = code.s[j];
node dp = Hash[k].dp[lst];
bool flag = 1;
for( int p = 0;p <= m;p ++ )
if( p ^ j and code.s[p] == up ) { flag = 0; break; }
if( j > 1 and ! flag ) {
code.s[j] = left;
insert( encode( code ), { dp.ans + L[i][j], dp.cnt * 2 % mod } );
code.s[j] = up;
}
if( i > 1 )
insert( encode( code ), { dp.ans + U[i][j], dp.cnt * 2 % mod } );
if( ! flag ) {
code.s[j] = 8;
insert( encode( code ), dp );
code.s[j] = up;
}
if( i > 1 and j > 1 and left ^ up ) {
for( int p = 0;p <= m;p ++ )
if( code.s[p] == left ) code.s[p] = up;
insert( encode( code ), { dp.ans + U[i][j] + L[i][j], dp.cnt * 3 % mod } );
}
}
}
node ans = { 0x7f7f7f7f, 0 };
for( int i = 1;i <= cnt[now];i ++ ) {
bool flag = 1;
zip code = decode( Hash[i].sta[now] );
for( int j = 1;j < m;j ++ ) flag &= ( code.s[j] == code.s[j + 1] );
if( flag ) ans = ans + Hash[i].dp[now];
}
printf( "Case #%lld: %lld %lld\n", t, ans.ans, ans.cnt );
}
return 0;
}
|