ICPC2017 Asia Regional Tsukuba E Black or White

Dむずくて通せてないですごめんなさい。

問題

n個のセルが黒または白で塗られている。
セルの初期状態およびゴールの状態が与えられる。
セルは一度の操作で連続する1〜k個を好きな色に塗ることができる。
最小で何回の操作で全てのセルをゴールの状態にできるか求めよ。

制約条件

n, k≦50万

解法

塗る区間Aは別の塗る区間Bに完全を内包する(orされる)か、全く交わらないかのどちらかであるとしてよい。何故ならAとBが交わるとき、交わった部分を短くして、A, Bを交わらないような塗り方に変えることができるため。


すると、i番目のセルまでゴールと同じ色にするコストの最小値をdp[i]とすれば、
(jまでが塗ってあり、j+1からiをまず1ストロークで一色に塗ってから、その内部を塗ると考えてよくて、)
dp[i] = min_i-k≦j {dp[j] + cost(j+1, i)}のように書ける。
ここでcost(j, i)はj番目からi番目のセルまでを塗るコストである。


ここで、jはi-k以上であったから、全ての区間は一回で塗ることができて、中に何色の色があるかだけでcost(j, i)が決まる。
色の個数をcとすればc/2+1回で塗ることができる。


iがゴール側で新しい色になっていたとき、(t[i] != t[i - 1]のとき)
i以下の色の種類は全て1が足される。cは切り捨てられるのでそのままだと扱いにくくて、
dp[j] + c(j+1, i) / 2 + 1を
(2*dp[j] + c(j+1, i)) / 2 + 1のように変形し、区間加算のできるRMQに2*dp[j] + (右から見たときの色の個数)をもっておくようにすれば操作がうまくいく。

ソースコード

template<class T>struct SegTree{
	T *dat, *lazy;
	int n;
	SegTree(int size = 1000000){
		for(n = 1; n < size; n *= 2);
		dat = new T[2 * n - 1];
		lazy= new T[2 * n - 1];
		init();
	}
	~SegTree(){ delete [] dat; delete [] lazy; }
	
	void init(){
		rep(i, 2 * n - 1) dat[i] = 0;
		rep(i, 2 * n - 1) lazy[i]= 0;
	}
	void add(int a, int b, ll v, int k, int l, int r){
		if(r <= a || b <= l) return;
		if(a <= l && r <= b){ lazy[k] += v; return; }
		if(lazy[k]){
			lazy[k * 2 + 1] += lazy[k];
			lazy[k * 2 + 2] += lazy[k]; lazy[k] = 0;
		}
		add(a, b, v, k * 2 + 1, l, (l + r) / 2);
		add(a, b, v, k * 2 + 2, (l + r) / 2, r);
		dat[k] = min(dat[k * 2 + 1] + lazy[k * 2 + 1], dat[k * 2 + 2] + lazy[k * 2 + 2]);
	}
	T query(int a, int b, int k, int l, int r){
		if(r <= a || b <= l) return inf;
		if(a <= l && r <= b) return dat[k] + lazy[k];
		if(lazy[k]){
			lazy[k * 2 + 1] += lazy[k];
			lazy[k * 2 + 2] += lazy[k]; lazy[k] = 0;
		}
		T vl = query(a, b, k * 2 + 1, l, (l + r) / 2);
		T vr = query(a, b, k * 2 + 2, (l + r) / 2, r);
		return min(vl, vr);
	}
	T query(int a, int b){ return query(a, b, 0, 0, n); }
	void add(int a, int b, ll v){ add(a, b, v, 0, 0, n); }
};
int main(){
	int n, len; cin >> n >> len;
	string s, t; cin >> s >> t;
	
	vi ans(n + 1);
	SegTree<int> seg(n + 2);
	rep(i, n){
		int best = inf;
		if(s[i] == t[i]) best = ans[i];
		if(i && t[i - 1] != t[i]) seg.add(0, i, 1);
		best = min(best, (seg.query(max(0, i + 1 - len), i + 1) + 1) / 2 + 1);
		
		seg.add(i + 1, i + 2, 2 * best);
		ans[i + 1] = best;
	}
	cout << ans[n] << endl;
	return 0;
}