kmjp's blog

競技プログラミング参加記です

yukicoder : No.579 3 x N グリッド上のサイクルのサイズ(hard)

これは解けないわ。
https://yukicoder.me/problems/no/578
https://yukicoder.me/problems/no/579

問題

縦4行、横(N+1)列の計4*(N+1)個の頂点が格子状に辺で結ばれているグラフを考える。
ここから任意の頂点を1つ選び、かつその頂点を含む単純閉路はいくつあるか。

解法

長さLの閉路内にはL個頂点があるので、この問題は結局ありえる単純閉路の長さの総和を求める問題となる。
単純閉路に関しては下記問題で扱った。
yukicoder : No.541 3 x N グリッド上のサイクルの個数 - kmjp's blog

上記問題では、状態遷移に関する行列Aの各要素は、状態遷移が存在するかしないかを0か1であらわしていた。
代わりに、この行列やベクトルの各要素を、単純なスカラー値でなく多項式とおく。
Aの要素について、辺をa個追加すると発生するような状態遷移を、1でなくyの多項式y^aと置こう。

これで先の問題と同じように行列累乗を行うと、yの多項式が登場する。
ここで、この式をyで微分するとy^aはa*y^aとなる。
つまり、辺の長さLであるような閉路の個数が、y^Lの係数となるようにすれば、これを微分してy=1を代入すると求める解が得られる。

さて、先の問題では(A^N)*v(vは初期状態に対応する要素が1でそれ以外0のベクトル)を求めればよかった。
今回はAはyの多項式であり、かつ微分してy=1を代入した値を求めたいので、(A^N)'*vを求めたい。
(A^N)を合成関数の微分の式の要領で分解すると、 (\sum_{i=0}^N A^iA'A^{N-1})vを求めればよいことになる。

EasyではNが小さいのでiを総当たりできるが、Hardではそうもいかない。
ここで T_N = (\sum_{i=0}^N A^iA'A^{N-1})とする。
 \displaystyle \begin{bmatrix}A&0\\A'&A\end{bmatrix}\begin{bmatrix}A^k\\T_{k-1}\end{bmatrix}=\begin{bmatrix}A^{k+1}\\T_{k}\end{bmatrix}
より、元のAの倍の大きさの行列を作れば行列累乗の要領でT_Nを求められる。

ll mo=1000000007;
const int MAT=20;
struct Mat { ll v[MAT][MAT]; };
Mat mulmat(Mat& a,Mat& b,int n=MAT) {
	ll mo2=4*mo*mo;
	int x,y,z; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(x,n) FOR(z,n) FOR(y,n) {
		r.v[x][y] += a.v[x][z]*b.v[z][y];
		if(r.v[x][y]>mo2) r.v[x][y] -= mo2;
	}
	FOR(x,n) FOR(y,n) r.v[x][y]%=mo;
	return r;
}

Mat powmat(ll p,Mat a,int n=MAT) {
	int i,x,y; Mat r;
	FOR(x,n) FOR(y,n) r.v[x][y]=0;
	FOR(i,n) r.v[i][i]=1;
	while(p) {
		if(p%2) r=mulmat(r,a,n);
		a=mulmat(a,a,n);
		p>>=1;
	}
	return r;
}

ll N;

string m[10]={
	"0.........",
	"1012...1..",
	"210112....",
	"3210212.1.",
	"1.1201....",
	"2.21101...",
	"1..2.101..",
	"...1...0..",
	"21....1.0.",
	".1231212.0",
};

Mat A,AD,B;
void solve() {
	int i,j,k,l,r,x,y; string s;
	
	FOR(y,10) FOR(x,10) {
		if(m[y][x]=='.') {
			A.v[y][x]=AD.v[y][x]=0;
		}
		else {
			A.v[y][x]=1;
			AD.v[y][x]=m[y][x]-'0';
			if(y>=1 && y<=6) AD.v[y][x]+=2;
			if(y>=7 && y<=8) AD.v[y][x]+=4;
		}
		B.v[y][x]=B.v[y+10][x+10]=A.v[y][x];
		B.v[y+10][x]=AD.v[y][x];
	}
	
	cin>>N;
	Mat T;
	T=powmat(N,B);
	ll ret=0;
	FOR(i,10) {
		ret+=T.v[19][i]*A.v[i][0]%mo;
		ret+=T.v[19][10+i]*AD.v[i][0]%mo;
	}
	
	cout<<ret%mo<<endl;
}

まとめ

微分を導入することも、その後の行列累乗に持ち込むことも、Editorialにある別解も、どれも思いつく気がしない…。