kmjp's blog

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

TopCoder SRM 798: Div1 Hard RaceCircuit

解法は自明だけどしんどい。
https://community.topcoder.com/stat?c=problem_statement&pm=16769&rd=18491

問題

R*Cのグリッドがある。
隣接マスを順にたどる経路のうち、全マスを1回ずつたどる単一の閉路で構成される例を考える。
各隣接マス間における移動コストと、各マスで経路を90度曲げる場合のコストが与えられる。
また、隣接マス間のうち移動不可の箇所もありうる。

コストの最小値を求めよ。

解法

yukicoderでグリッド上の閉路を扱う問題が何度か出ている。
No.234 めぐるはめぐる (4) - yukicoder
No.541 3 x N グリッド上のサイクルの個数 - yukicoder
No.579 3 x N グリッド上のサイクルのサイズ(hard) - yukicoder

ここらへんを応用しよう。
1段ごとに、次の段に経路が伸びている列と、あとどの列とどの列が経路上で連結しているかを状態に持ち、その際のコストの最小値を覚えておく。
次の段において、90度曲げる箇所を(2^C通り)総当たりしていこう。
列の連結関係が変わるので、そこは面倒だが頑張って処理する。
最終段以外で途中閉路が閉じてしまうケースや、最終段で複数閉路が生じるケースを丁寧に除外していこう。

以下のコードは最大ケースで2.6sと結構危ない。

unordered_map<ll,ll> from;
unordered_map<ll,ll> to;
ll LR[15][15],UD[15][15],CN[15][15];
int ov[256],con[256];
ll DM[1<<12],CM[1<<12];

class RaceCircuit {
	public:
	int construct(int R, int C, vector <int> costRight, vector <int> costDown, vector <int> costCorner) {
		int mask,i,j,y,x;
		FOR(y,R) FOR(x,C) {
			LR[y][x]=costRight[y*C+x];
			if(LR[y][x]==-1) LR[y][x]=1LL<<30;
			UD[y][x]=costDown[y*C+x];
			if(UD[y][x]==-1) UD[y][x]=1LL<<30;
			CN[y][x]=costCorner[y*C+x];
		}
		
		vector<int> cand;
		FOR(mask,1<<C) if(__builtin_popcount(mask)%2==0) cand.push_back(mask);
		
		from.clear();
		FOR(mask,1<<C) {
			ll state=0;
			ll sum=0;
			int right=0,p=0;
			FOR(x,C) {
				if(mask&(1<<x)) {
					sum+=UD[0][x]+CN[0][x];
					if(right==0) {
						p=x;
					}
					else {
						state|=(p+1LL)<<(4*x);
						state|=(x+1LL)<<(4*p);
					}
					right^=1;
				}
				else {
					if(right==0) sum=1LL<<30;
				}
				if(right) sum+=LR[0][x];
			}
			if(right||state==0||sum>=1LL<<30) continue;
			from[state]=sum;
		}
		for(y=1;y<R-1;y++) {
			FOR(mask,1<<C) {
				DM[mask]=CM[mask]=0;
				FOR(x,C) if(mask&(1<<x)) DM[mask]+=UD[y][x], CM[mask]+=CN[y][x];
			}
			to.clear();
			FORR(f,from) {
				ll sum=f.second;
				ll state=f.first;
				int up=0;
				int num=0;
				FOR(x,C) {
					int s=(state>>(4*x))&15;
					if(s) up|=1<<x, ov[x]=s-1,num++;
				}
				FORR(mask,cand) {
					int down=up^mask;
					if(down==0) continue;
					ll ret=sum+DM[down]+CM[mask];
					if(ret>=1LL<<30) continue;
					int wall=up&down;
					int right=0;
					int start=-100;
					FOR(x,C) {
						if(wall&(1<<x)) {
							if(right) {
								ret=1LL<<30;
								break;
							}
							con[x]=x+100;
							con[x+100]=x;
						}
						else if(right) {
							if(up&(1<<x)) {
								con[start]=x;
								con[x]=start;
								right=0;
							}
							else if(down&(1<<x)) {
								con[start]=100+x;
								con[100+x]=start;
								right=0;
							}
						}
						else {
							if(up&(1<<x)) {
								right=1;
								start=x;
							}
							else if(down&(1<<x)) {
								right=1;
								start=100+x;
							}
							else {
								ret+=1LL<<30;
								break;
							}
						}
						if(right) ret+=LR[y][x];
					}
					if(ret>=1LL<<30) continue;
					
					ll nstate=0;
					int cover=0;
					FOR(x,C) if(down&(1<<x)) {
						int tar=con[100+x];
						while(tar<100) tar=con[ov[tar]], cover+=2;
						tar-=100;
						down^=(1<<x)|(1<<tar);
						nstate|=(x+1LL)<<(4*tar);
						nstate|=(tar+1LL)<<(4*x);
					}
					if(nstate==0) continue;
					if(cover!=num) continue;
					if(to.count(nstate)==0) to[nstate]=1LL<<30;
					to[nstate]=min(to[nstate],ret);
				}
			}
			
			swap(from,to);
		}
		ll mi=1LL<<30;
		FORR(f,from) {
			ll state=f.first;
			ll sum=f.second;
			
			FOR(x,C) {
				int s=(state>>(4*x))&15;
				sum+=LR[R-1][x];
				if(x==0&&s==0) sum+=1LL<<30;
				if(s) {
					s--;
					if(x==0&&s!=C-1) sum+=1LL<<30;
					if(x>0&&x<s&&x+1!=s) sum+=1LL<<30;
					if(x==0) sum-=LR[R-1][C-1];
					else if(x<s) sum-=LR[R-1][x];
					sum+=CN[R-1][x];
				}
			}
			mi=min(mi,sum);
		}
		if(mi>=1LL<<30) mi=-1;
		return mi;
	}
}

まとめ

今回1段ずつ処理したけど、よくある1マスずつの処理に直すと高速化できる問題だったかな?
どっちにせよ短時間で詰め切るのはしんどい。