kmjp's blog

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

TopCoder SRM 659 Div1 Medium CampLunch

豹本に似た問題があったね。
http://community.topcoder.com/stat?c=problem_statement&pm=13713

問題

M人の人がN回食事をとる。
食事は毎回M人が1列に並んで食事をする。

各人に与えられる食事のパターンは以下のいずれかである。

  • 1人分の食事を与えられ、1回の食事で食べきる。
  • 2人分の食事を与えられ、1人で2回分の食事で食べきる。
  • 2人分の食事を与えられ、隣の席の2人で一緒に1回の食事で食べきる。

食事は毎回過不足なく全員に行きわたるとする。
そのような全体の食事のパターン数を求めよ。

解法

豹本p.349、床板の問題のアイデアを借用できる。
すなわち、y回目の食事で2回分の食事を与えられてしまった人をbitmapで管理し、その人は(y+1)日目には食事を与えられない、と考えてDPで解ける。
ただ、この場合O(N*2^2M)ほどかかってしまいTLEする。

ここで高速ゼータ変換の要領で計算量をO(N*M*2^M)に落とすことを考える。
y回目に取った食事が1人前か2人前(2人で2人前食べたか、(y-1)回とy回で2回分1人で食べたか)かどうかをM人分bitmaskで表し、1~y回目までの食事の組み合わせ数をdp[y][mask]とする。

y+1回目の食事の組み合わせを考える際、まずは2人隣同士の人で食事を分けるパターンを無視し、y回目と(y+1)回目で1人が2人分の食事を食べきるケースを考える。
y回目と(y+1)回目で1人が2人分の食事を食べきるためには、y回目の時点だけみれば1人分の食事を取っている場合、(y+1)回目と合わせて2人分の食事をとれる。
よってdp[y+1][mask]は以下のように計算できる。
 \displaystyle dp[y+1][mask] = \sum_{m \& mask == 0} dp[y][m]

上記計算を各mask,mについて行うとO(2^2M)かかりTLEする。
しかしここで高速ゼータ変換の要領で計算するとO(M*2^M)に抑えられる。

その後、隣の席の2人で食事をとるケースについて、同様にO(M*2^M)で値を更新していく。
すなわちa番目の人とb番目の人が隣接している場合、(mask & (2^a | 2^b))==0であるmaskに対して
 \displaystyle dp[y+1][mask] += \sum_{m \& (2^a | 2^b) == (2^a | 2^b)} dp[y+1][m]
で計算できる。

ll mo=1000000007;
ll dp[1<<16];

class CampLunch {
	public:
	
	int count(int N, int M, vector <string> a) {
		int i,x,y,mask,mask2;
		
		ZERO(dp);
		dp[0]=1;
		
		FOR(y,N) {
			FOR(x,M) {
				FOR(mask,1<<M) if((mask & (1<<x))==0) (dp[mask]+=dp[mask^ (1<<x)])%=mo;
			}
			
			FOR(x,M-1) {
				int fil =(1<<(a[y][x]-'A')) | (1<<(a[y][x+1]-'A'));
				FOR(mask,1<<M) if((mask&fil)==0) (dp[mask | fil] += dp[mask])%=mo;
			}
			
			FOR(mask,1<<M) {
				mask2 = ((1<<M)-1)^mask;
				if(mask<mask2) swap(dp[mask],dp[mask2]);
			}
		}
		ll ret=0;
		FOR(mask,1<<M) ret += dp[mask];
		
		return ret%mo;
	}
}

まとめ

高速ゼータ変換、なかなかさらっと書けないんだよなぁ。