ural 1519 fomular 1 既插头DP学习笔记

直接看CDQ在2008年的论文吧.
个人认为她的论文有两个不明确的地方, 这里补充一下: 首先是轮廓的概念. 我们在进行插头DP时, 是从上往下, 从左往右逐个格子进行的, 已经处理的格子与未经处理的格子之间的分界线叫做轮廓线. 因此每个时刻轮廓线的长度都为列数加一. 每次处理下一个格子时, 有且仅有两条轮廓线会变动.
至于什么是插头, 这个很好理解, 就是从格子里面连出来的线就叫做插头. 不难看出, 在本题中, 一个不可选的格子没有插头; 一个可选的格子有且仅有两个插头. 穿过轮廓线的插头叫做轮廓线上的插头.
剩下的自己看即可.
考虑如何DP, 根据论文的描述, 我们发现轮廓线上的插头可以被看作是一个括号序列; 一个合法的状态要求轮廓线也是合法的. 同时为了限制只能存在一条路径, 我们需要在代码中加以体现.

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 15, M = 15, SZ = (int)3e5, STT = 1594323;
int n, m;
char s[N][M];
int pw[M], enc[STT], dec[SZ], tp; // 分别表示加密和解密
long long f[SZ], g[SZ];
inline int check(int stt)
{
	int cnt = 0;
	for(int i = 0; i < m + 1; ++ i)
	{
		if(stt / pw[i] % 3 == 1) ++ cnt; else if(stt / pw[i] % 3 == 2) -- cnt;
		if(cnt < 0) return 0;
	}
	return ! cnt;
}
inline void initialize()
{
	pw[0] = 1; for(int i = 1; i <= m + 1; ++ i) pw[i] = pw[i - 1] * 3;
	tp = 0;
	for(int i = 0; i < pw[m + 1]; ++ i) if(check(i)) enc[i] = tp, dec[tp ++] = i;
}
inline int get(int stt, int pos) {return stt / pw[pos] % 3;}
int main()
{

#ifndef ONLINE_JUDGE

	freopen("ural1519.in", "r", stdin);
	freopen("ural1519.out", "w", stdout);

#endif

	int lstX = -1, lstY = -1;
	scanf("%d %d
", &n, &m); for(int i = 0; i < n; ++ i) scanf("%s", s[i]);
	for(int i = 0; i < n; ++ i) for(int j = 0; j < m; ++ j) if(s[i][j] == '.') lstX = i, lstY = j;
	initialize();
	memset(g, 0, sizeof(g)); g[enc[0]] = 1;
	for(int i = 0; i < n; ++ i)
	{
		for(int j = 0; j < m; ++ j)
		{
			swap(f, g); memset(g, 0, sizeof(g));
			if(s[i][j] == '*')
			{
				for(int k = 0; k < tp; ++ k) if(! get(dec[k], j) && ! get(dec[k], j + 1)) g[k] = f[k];
				continue;
			}
			else for(int k = 0; k < tp; ++ k) if(f[k])
			{
				int x = get(dec[k], j), y = get(dec[k], j + 1);
				if(! x && ! y) g[enc[dec[k] + pw[j] + pw[j + 1] * 2]] += f[k];
				else if(! x ^ ! y)
				{
					g[k] += f[k];
					g[enc[dec[k] + (y - x) * pw[j] + (x - y) * pw[j + 1]]] += f[k];
				}
				else if(x == 1 && y == 1)
				{
					int p = j + 1, cnt = 0;
					for(; p <= m; ++ p)
					{
						if(get(dec[k], p) == 1) ++ cnt; else if(get(dec[k], p) == 2) -- cnt;
						if(! cnt) break;
					}
					g[enc[dec[k] - pw[j] - pw[j + 1] - pw[p]]] += f[k];
				}
				else if(x == 2 && y == 2)
				{
					int p = j, cnt = 0;
					for(; ~ p; -- p)
					{
						if(get(dec[k], p) == 2) ++ cnt; else if(get(dec[k], p) == 1) -- cnt;
						if(! cnt) break;
					}
					g[enc[dec[k] - 2 * pw[j] - 2 * pw[j + 1] + pw[p]]] += f[k];
				}
				else if(x == 1 && y == 2 && i == lstX && j == lstY) g[enc[dec[k] - pw[j] - 2 * pw[j + 1]]] += f[k];
				// 这里就是限制只能存在一条路径的关键
				else if(x == 2 && y == 1) g[enc[dec[k] - 2 * pw[j] - pw[j + 1]]] += f[k];
			}
 		}
 		swap(f, g); memset(g, 0, sizeof(g));
 		for(int j = 0; j < tp && dec[j] < pw[m]; ++ j) g[enc[dec[j] * 3]] += f[j];
	}
	printf("%lld
", f[enc[0]]);
}

原文地址:https://www.cnblogs.com/ZeonfaiHo/p/7490321.html