kmjp's blog

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

Codeforces #631 Div1 D. Dreamoon Likes Strings

これまた実装の面倒な問題。
http://codeforces.com/contest/1329/problem/D

問題

文字列Sが美しいとは、隣接する文字同士がすべて異なるものをいう。
今ここで文字列Aが与えられる。
Aから美しい部分列を削除し、間を詰めるということを繰り返して、Aを空にしたい。
削除回数の最小値とその手順を答えよ。

解法

文字iが連続している個数をC[i]とする。
文字列Aにおける最小必要処理回数をf(A)とすると、f(A)=max(ceil(sum(C)/2), max(C))+1となる。
Aをどう処理してもsum(C)を2減らすかmax(C)を1減らすかその両方しかできないので、最大値を減らす方策をとる。

  • 前者を減らすには、aaxyzbbの形をしているところからaxyzbを削除しabとする。aaかbbに最大値を取るところを選べば、両方を同時に達成できる。
  • 後者を減らすには、aaの形をしてるところでaを1つ削除する

回数はともかく、手順を取るのが結構面倒。
頑張ってBITなど使って添え字の変化を細かく追うしかない。

int T;
int N;
string S;

template<class V, int ME> class BIT {
public:
	V bit[1<<ME];
	V operator()(int e) {if(e<0) return 0;V s=0;e++;while(e) s+=bit[e-1],e-=e&-e; return s;}
	void add(int e,V v) { e++; while(e<=1<<ME) bit[e-1]+=v,e+=e&-e;}
};
BIT<int,20> bt;

int range[202020];
int ri[202020];
int p1[202020],p2[202020];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>T;
	int ot=T;
	while(T--) {
		cin>>S;
		N=S.size();
		set<int> pat[27][27];
		int cnt[27]={};
		set<int> pos;
		set<int> alive;
		int lef=0;
		cnt[26]=2;
		FOR(i,N) {
			alive.insert(i);
			bt.add(i,1);
			assert(bt(i)==i+1);
			if(i&&S[i]==S[i-1]) {
				cnt[S[i]-'a']++;
				pos.insert(lef);
				range[lef]=i;
				ri[lef]=i-1;
				lef=i;
			}
		}
		range[lef]=N;
		ri[lef]=N-1;
		pos.insert(lef);
		
		FORR(p,pos) {
			if(p==0) {
				p1[p]=26;
			}
			else {
				p1[p]=S[p]-'a';
			}
			if(ri[p]==N-1) {
				p2[p]=26;
			}
			else {
				p2[p]=S[ri[p]]-'a';
			}
			pat[p1[p]][p2[p]].insert(p);
		}
		
		vector<pair<int,int>> ret;
		while(bt(N)) {
			int ma=0,id=26;
			FOR(i,26) if(cnt[i]>ma) ma=cnt[i],id=i;
			
			int ma2=0,id2=0;
			FOR(i,26) if(i!=id) {
				if(pat[id][i].size()+pat[i][id].size()>ma2) {
					ma2=pat[id][i].size()+pat[i][id].size();
					id2=i;
				}
			}
			
			int cur;
			if(ma==0 || ma2==0) {
				cur=*pos.begin();
			}
			else {
				if(pat[id][id2].size()) {
					cur=*pat[id][id2].begin();
					pat[id][id2].erase(pat[id][id2].begin());
				}
				else {
					cur=*pat[id2][id].begin();
					pat[id2][id].erase(pat[id2][id].begin());
				}
			}
			ret.push_back({bt(cur),bt(range[cur]-1)});
			{
				auto it=alive.lower_bound(cur);
				while(1) {
					if(it==alive.end() || *it>=range[cur]) break;
					bt.add(*it,-1);
					it=alive.erase(it);
				}
			}
			cnt[p1[cur]]--;
			cnt[p2[cur]]--;
			pos.erase(cur);
			
			if(pos.empty()) break;
			auto it=alive.lower_bound(cur);
			if(it==alive.end()) {
				it=pos.lower_bound(cur);
				x=*prev(it);
				
				cnt[p2[x]]--;
				pat[p1[x]][p2[x]].erase(x);
				p2[x]=26;
				cnt[p2[x]]++;
				pat[p1[x]][p2[x]].insert(x);
				continue;
			}
			if(it==alive.begin()) {
				x=*it;
				cnt[p1[x]]--;
				pat[p1[x]][p2[x]].erase(x);
				p1[x]=26;
				cnt[p1[x]]++;
				pat[p1[x]][p2[x]].insert(x);
				continue;
			}
			x=*it;
			y=*prev(it);
			if(S[x]==S[y]) {
				cnt[p2[x]]++;
			}
			else {
				
				auto it=pos.lower_bound(cur);
				y=*it;
				x=*prev(it);
				//cout<<cur<<" "<<x<<" "<<y<<endl;
				pos.erase(y);
				pat[p1[x]][p2[x]].erase(x);
				pat[p1[y]][p2[y]].erase(y);
				p2[x]=p2[y];
				pat[p1[x]][p2[x]].insert(x);
				range[x]=range[y];
				ri[x]=ri[y];
			}
		}
		/*
		if(ot==22982 && ot-T!=3716) continue;
		if(ot==22982) cout<<S<<endl;
		*/
		cout<<ret.size()<<endl;
		FORR(r,ret) cout<<r.first<<" "<<r.second<<endl;
		
		
		
	}
}

まとめ

回数だけだったらずっと簡単なのにね。