TopCoder SRM523 Div1Hard

問題

高々21*21の盤面が与えられる。各マスは'.'か、アルファベットが書かれている。アルファベットは21種類のうちどれかである。今、この盤面上で「パス」を考える。パスは、iマス目がi+1マス目に隣接している(1 <= i <= 20)ような21個の互いに異なるマスの組み合わせである。パスのうち、構成するマスに21種類のアルファベットがすべて現れるようなパスは何個あるか。

解法

まず、パスの定義に「互いに異なる」とあるが、これは無視してもよいことがわかる。なぜなら、同じマスを通ったら21種類のアルファベットが現れることなどありえないからである。
すると、11マス目を固定して、そこを起点とする長さ11のパスを考え、パス構成するアルファベットが(起点マス以外において)異なるような2つのパスを併合してやると、自然と求めるパスになっている。そのようなものを数えればよい。
これを数えるのは、起点からDFSをすればよい。考えうる場合の数は高々4 * 3^10 = 236196通りしかない(実際はもっと少ない)。併合は、この少なさを利用して適当にやるとできる。

コード

#include <cstdio>
#include <vector>
#include <string>

using namespace std;

class AlphabetPaths
{
	int W, H, in[23][23];
	bool visited[23][23];
public:

	int bits[236196], bLast;
	int tmp[1<<21];
	
	void visit(int p, int q, int mask, int rem)
	{
		if(in[p][q]==-1) return;
		if(mask & in[p][q]) return;
		mask |= in[p][q];
		rem--;
		
		if(rem==0){
			bits[bLast++] = mask;
			return;
		}
		
		visit(p-1, q, mask, rem);
		visit(p+1, q, mask, rem);
		visit(p, q-1, mask, rem);
		visit(p, q+1, mask, rem);
	}
	
	long long solve(int p, int q)
	{
		if(in[p][q]==-1) return 0;
		
		bLast = 0;
		visit(p, q, 0, 11);
		
		for(int i=0;i<bLast;i++) tmp[bits[i]]++;
		int xorMask = ((1<<21) - 1) ^ in[p][q];
		long long ret = 0;
		
		for(int i=0;i<bLast;i++){
			ret += (long long)tmp[bits[i]] * tmp[xorMask ^ bits[i]] * 2;
			tmp[bits[i]] = 0;
			tmp[xorMask ^ bits[i]] = 0;
		}
		
		return ret;
	}
	
	int conv(char c)
	{
		char tbl[] = "ABCDEFZHIKLMNOPQRSTVX";
		for(int i=0;i<21;i++) if(tbl[i]==c) return i;
		return -1;
	}
	
	long long count(vector<string> lt)
	{
		W = lt[0].size();
		H = lt.size();
		
		for(int i=0;i<H+2;i++)
			for(int j=0;j<W+2;j++){
				in[i][j] = -1;
				visited[i][j] = false;
			}
		
		for(int i=0;i<H;i++)
			for(int j=0;j<W;j++){
				if(lt[i][j]!='.') in[i+1][j+1] = 1 << conv(lt[i][j]);
			}
		
		for(int i=0;i<(1<<21);i++) tmp[i] = 0;
		
		long long ret = 0;
		for(int i=1;i<=H;i++){
			for(int j=1;j<=W;j++){
				ret += solve(i, j);
			}
		}
		
		return ret;
	}
};