zoukankan      html  css  js  c++  java
  • 学习笔记:插头DP

    基于连通性的状压DP问题。

    一般是给你一个网格,有一些连通性的限制。

    例题

    插头DP模板

    链接

    题意:网格图,去掉一些点,求哈密顿回路方案数。

    一般按格递推(从上到下,从左到右)。

    每个格子要从四个方向中选两个作出边。

    我们只需要记录红色的轮廓线的状态,是否有边伸出这个线(称之为插头), 还要记录伸出来的边的连通性。

    记录连通性的方法:

    1. 最小表示法
    2. 括号表示法(适用范围较小,效率一般更高):出边是两两配对的(如果没有回来就有终点了);并且出边不可能交叉(因为如果有交叉就经过重复点了)。咱们用三进制表示,(() 对应 (1)()) 对应 (2),没有边对应 (0)。看似最坏状态是 (3 ^ {n + 1}) 的,但要保证括号配对,所以大概有效状态会很少,因此不要以最坏复杂度来分析插头 DP,大概可以打个表式一下极限数据。

    (f_{i, j, s}) 为考虑到 (i, j) 当前轮廓线状态是 (S) 的方案数。

    分类讨论,设上状态为 (y),左状态为 (x)

    1. 如果 ((i, j)) 是障碍物。需要 (x = y = 0)。状态不变。
    2. 否则,若 (x = y = 0),则 (x gets 1, y gets 1)
    3. (x = 0)(y ot= 0),枚举一下 (y) 从下面和右边出去两种情况。
    4. (x ot= 0)(y = 0),同 3,向下或向右走。
    5. (x = y = 1),必然要连起来,把右边配对的两个插头较左的 (2) 变成 (1)。(即 (y) 的配对变成 (1)
    6. (x = y = 2),同 5 ,把 (x) 对应的配对插头变成 (2)
    7. (x = 2, y = 1),把两个插头去掉赋 (0)
    8. (x = 1, y = 2),将整个回路封死,只能在整个格子的最后一个格子去封。(只会发生在最后一个格子)。

    想要把代码变得美一点、短一点,大概是做到了吧...
    这里没有用哈希表,把 (42000) 个状态先 dfs 出来,存到数组里,再预处理一下每个状态每对括号的匹配。

    这样每次转移可以 (O(1)),但是由于状态对应到编号我用的二分,所以复杂度是 (O(n^2 S log S)),其中 (S) 是总状态数大概是 (S le 42000)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <unordered_map>
    using namespace std;
    
    typedef long long LL;
    
    const int N = 15, S = 42000;
    
    int n, m, d[S], tot, w, c[N], p[S][N], now[N], top, s[N], L;
    int ex, ey;
    LL ans, f[2][S], h[S];
    bool st[N][N];
    char g[N][N];
    
    
    int inline query(int x) { return lower_bound(d + 1, d + 1 + tot, x) - d; }
    int inline ask(int x, int i) { return x >> (2 * i) & 3; }
    int inline get(int i, int t) { return t << (2 * i); }
    void inline add(int a, int b) { f[w][query(b)] += f[!w][a]; }
    
    
    void inline work(int x) {
        memset(now, -1, sizeof now); top = 0;
    	for (int i = 0; i < 2 * L; i += 2) {
    		int t = x >> i & 3;
    		if (t == 1) s[++top] = i >> 1;
    		else if (t == 2) now[s[top]] = i >> 1, now[i >> 1] = s[top--];
    	}
    	d[++tot] = x;
    	for (int i = 0; i < L; i++) p[tot][i] = now[i];
    }
    
    void dfs(int u, int s, int cnt) {
        if (u == -1) { if (!cnt) work(s); return ; }
        dfs(u - 1, s, cnt);
        if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);
        if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);
    }
    
    int main() {
    	scanf("%d%d", &n, &m); L = m + 1;
    	for (int i = 1; i <= n; i++) {
    		scanf("%s", g[i] + 1);
    		for (int j = 1; j <= m; j++)
    			if (g[i][j] == '.') st[i][j] = true, ex = i, ey = j;
    	}
    	dfs(L - 1, 0, 0);
    	f[0][1] = 1;
    	for (int i = 1; i <= n; i++) {
    		memset(h, 0, sizeof h);
    		for (int j = 1; j <= tot; j++)
    			if (!ask(d[j], L - 1)) h[query(d[j] << 2)] += f[w][j];
    		memcpy(f[w], h, sizeof h);
    		for (int j = 1; j <= m; j++) {
    			w ^= 1; memset(f[w], 0, sizeof f[w]);
    			for (int u = 1; u <= tot; u++) {
    				if (!f[!w][u]) continue;
    				int x = ask(d[u], j - 1), y = ask(d[u], j);
    				if (!st[i][j]) {
    					if (!x && !y) add(u, d[u]);
    				} else if (!x && !y) add(u, d[u] + get(j - 1, 1) + get(j, 2));
    				else if (!x && y) add(u, d[u]), add(u, d[u] + get(j - 1, y) - get(j, y));
    				else if (x && !y) add(u, d[u]), add(u, d[u] - get(j - 1, x) + get(j, x));
    				else if (x == 1 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y) - get(p[u][j], 1));
    				else if (x == 2 && y == 2) add(u, d[u] - get(j - 1, x) - get(j, y) + get(p[u][j - 1], 1));
    				else if (x == 2 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y));
    				else if (x == 1 && y == 2 && i == ex && j == ey && d[u] - get(j - 1, 1) - get(j, 2) == 0) ans += f[!w][u];
    			}
    		}
    	}
    	printf("%lld
    ", ans);
    	return 0;
    }
    

    HNOI2007 神奇游乐园

    题意:有权网格图,求回路最大权值。

    状态同上面,可以用括号序列维护(两两配对)。

    转移稍有不同,每个封口都可以给 ( ext{ans}) 贡献,另外上题的分类 1 可以考虑不选这个格子。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int N = 105, M = 7, S = 42000, INF = 0xcfcfcfcf;
    
    int n, m, a[N][M], d[S], tot, w, c[M], p[S][M], now[M], top, s[M], L;
    int ans = -2e9, f[2][S], h[S];
    
    int inline query(int x) { return lower_bound(d + 1, d + 1 + tot, x) - d; }
    int inline ask(int x, int i) { return x >> (2 * i) & 3; }
    int inline get(int i, int t) { return t << (2 * i); }
    void inline add(int a, int b, int v) { f[w][query(b)] = max(f[w][query(b)], f[!w][a] + v); }
    
    
    void inline work(int x) {
        memset(now, -1, sizeof now); top = 0;
    	for (int i = 0; i < 2 * L; i += 2) {
    		int t = x >> i & 3;
    		if (t == 1) s[++top] = i >> 1;
    		else if (t == 2) now[s[top]] = i >> 1, now[i >> 1] = s[top--];
    	}
    	d[++tot] = x; 
    	for (int i = 0; i < L; i++) p[tot][i] = now[i];
    }
    
    void dfs(int u, int s, int cnt) {
        if (u == -1) { if (!cnt) work(s); return ; }
        dfs(u - 1, s, cnt);
        if (cnt) dfs(u - 1, s + get(u, 1), cnt - 1);
        if (cnt + 1 <= u) dfs(u - 1, s + get(u, 2), cnt + 1);
    }
    
    int main() {
    	scanf("%d%d", &n, &m); L = m + 1;
    	for (int i = 1; i <= n; i++) 
    		for (int j = 1; j <= m; j++)
    			scanf("%d", &a[i][j]);
    	dfs(L - 1, 0, 0);
    	memset(f, 0xcf, sizeof f);
    	f[0][1] = 0;
    	for (int i = 1; i <= n; i++) {
    		memset(h, 0xcf, sizeof h);
    		for (int j = 1; j <= tot; j++)
    			if (!ask(d[j], L - 1)) h[query(d[j] << 2)] = max(h[query(d[j] << 2)], f[w][j]);
    		memcpy(f[w], h, sizeof h);
    		for (int j = 1; j <= m; j++) {
    			w ^= 1; memset(f[w], 0xcf, sizeof f[w]);
    			for (int u = 1; u <= tot; u++) {
    				if (f[!w][u] == INF) continue;
    				int x = ask(d[u], j - 1), y = ask(d[u], j);
    				if (!x && !y) add(u, d[u] + get(j - 1, 1) + get(j, 2), a[i][j]), add(u, d[u], 0);
    				else if (!x && y) add(u, d[u], a[i][j]), add(u, d[u] + get(j - 1, y) - get(j, y), a[i][j]);
    				else if (x && !y) add(u, d[u], a[i][j]), add(u, d[u] - get(j - 1, x) + get(j, x), a[i][j]);
    				else if (x == 1 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y) - get(p[u][j], 1), a[i][j]);
    				else if (x == 2 && y == 2) add(u, d[u] - get(j - 1, x) - get(j, y) + get(p[u][j - 1], 1), a[i][j]);
    				else if (x == 2 && y == 1) add(u, d[u] - get(j - 1, x) - get(j, y), a[i][j]);
    				else if (x == 1 && y == 2 && d[u] - get(j - 1, 1) - get(j, 2) == 0) ans = max(ans, f[!w][u] + a[i][j]);
    			}
    		}
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
    

    SCOI2011 地板

    题意:用 L 铺满网格图(有障碍物)的方案数。

    不需要存连通性,要存每个插头有没有拐弯,三进制状态就可以。

    选行列短的当列做,这样状态总数就是 (le 3 ^ {11}) 的。

    写了一次三进制,貌似蛮好写的,预处理出来 (3) 的幂次(也就是权),这样模拟位运算都是 (O(1)) 的。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    const int N = 105, M = 12, S = 180000, P = 20110520;
    
    int n, m, ex, ey, ans, Pow[M], f[2][S], w, h[S];
    
    char g[N][N];
    
    bool st[N][N];
    
    int inline ask(int x, int i) { return x / Pow[i] % 3; }
    int inline get(int i, int t) { return t * Pow[i]; }
    void inline add(int a, int b) { (f[w][b] += f[!w][a]) %= P; }
    
    void inline out(int x) {
        for (int i = 0; i <= m; i++) {
            cout << (x % 3);
            x /= 3;
        }
    }
    int main() {
    	Pow[0] = 1;
    	for (int i = 1; i < M; i++) Pow[i] = Pow[i - 1] * 3;
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++) scanf("%s", g[i] + 1);
    	if (m > n) {
    		for (int i = 1; i <= m; i++)
    			for (int j = 1; j < i; j++) swap(g[i][j], g[j][i]);
    		swap(m, n);
    	}
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= m; j++)
    			if (g[i][j] == '_') ex = i, ey = j, st[i][j] = true;
    	f[0][0] = 1;
    	for (int i = 1; i <= n; i++) {
    		memset(h, 0, sizeof h);
    		for (int j = 0; j < Pow[m + 1]; j++)
    			if (!ask(j, m)) (h[j * 3] += f[w][j]) %= P;
    		memcpy(f[w], h, sizeof h);
    		for (int j = 1; j <= m; j++) {
    			w ^= 1, memset(f[w], 0, sizeof f[w]);
    			for (int u = 0; u < Pow[m + 1]; u++) {
    				if (!f[!w][u]) continue;
    				int x = ask(u, j - 1), y = ask(u, j);
    				if (!st[i][j]) {
    					if (!x && !y) add(u, u);
    				} else if (!x && !y) add(u, u + get(j - 1, 2) + get(j, 2)), add(u, u + get(j - 1, 1)), add(u, u + get(j, 1));
    				else if (x == 0 && y == 1) add(u, u + get(j, 1)), add(u, u - get(j, 1) + get(j - 1, 1));
    				else if (x == 0 && y == 2) {
    					add(u, u - get(j, 2) + get(j - 1, 2));
    					add(u, u - get(j, 2));
    					if (i == ex && j == ey && u - get(j, 2) == 0) (ans += f[!w][u]) %= P;
    				} else if (x == 1 && y == 0) add(u, u + get(j - 1, 1)), add(u, u - get(j - 1, 1) + get(j, 1));
    				else if (x == 1 && y == 1) {
    					add(u, u - get(j - 1, 1) - get(j, 1));
    					if (i == ex && j == ey && u - get(j - 1, 1) - get(j, 1) == 0) (ans += f[!w][u]) %= P;
    				} else if (x == 2 && y == 0) {
    					add(u, u - get(j - 1, 2) + get(j, 2));
    					add(u, u - get(j - 1, 2));
    					if (i == ex && j == ey && u - get(j - 1, 2) == 0) (ans += f[!w][u]) %= P;
    				}
    			}
    		}
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    主机连接虚拟机redis 服务器
    在dockers中调试dump的dotnet程序
    我的devops实践经验分享一二
    【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--请求处理结果适配篇(7/8)
    【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇(6/8)
    【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--请求处理函数装饰器注册篇(5/8)【controller+action】
    【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--控制器和处理函数的注册篇(4/8)【controller+action】
    【nodejs】让nodejs像后端mvc框架(asp.net mvc )一样处理请求--控制器的声明定义和发现篇(3/8)
    【nodejs】让nodejs像后端mvc框架(asp.net mvc )一样处理请求--路由限制及选择篇(2/8)【route】
    【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--目录(8/8 完结)
  • 原文地址:https://www.cnblogs.com/dmoransky/p/14111781.html
Copyright © 2011-2022 走看看