kmjp's blog

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

yukicoder : No.2553 Holidays

場合分けが大変な問題。
https://yukicoder.me/problems/no/2553

問題

N日分のカレンダーがある。
いくつかの日は平日固定または休日に固定されている。
残りの日のうち、M日を休日にできる。また、固定されていない日のうち、前後が休日の日は、休日になる。

最適な日を休日にした時、最大何日休日にできるか。

解法

選択可能な連続した日に対し、

  • 前後の休日の数
  • 日数の偶奇

で分担しよう。
1日休日を設定することで、極力多く休日を増やせるようにする。

以下貪欲に行おう。

  • 前後が休日で、選択可能な日が奇数の区間を処理する。
    • 区間が短い順に、1日おきに休日を設定しよう。休日の設定数×2以上休日を増やせる。
  • 以下を同様に処理する。
    • 前後が休日で、選択可能な日が偶数の区間を処理する。
    • 前後いずれかが休日の区間を処理する。
      • 基本的に1日休日を設定すると、休日を2日増やせる。後者は区間が奇数の場合1日余るが、これはいったん残しておく。
  • 以下を同様に処理する。
    • 前後が平日の区間を処理する。
    • 区間が長い順に、端に休日を設定してそこから1日おきに休日を設定する。

これでもまだ休日に設定可能な日数が残っているなら、残った選択可能な日を休日にしよう。

int N,M;
string S;

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N>>M>>S;
	vector<int> V;
	int ret=0;
	FOR(i,N) {
		if(S[i]!='-') V.push_back(i);
		if(S[i]=='o') ret++;
	}
	vector<int> C[3][2];
	FOR(i,V.size()-1) if(V[i+1]-V[i]>1) {
		C[(S[V[i]]=='o')+(S[V[i+1]]=='o')][(V[i+1]-V[i]-1)%2].push_back(V[i+1]-V[i]-1);
	}
	FOR(x,3) FOR(y,2) sort(ALL(C[x][y]));
	
	FORR(v,C[2][1]) {
		if(M>=v/2) {
			M-=v/2;
			ret+=v;
		}
		else {
			ret+=M*2;
			M=0;
		}
	}
	vector<int> CC;
	FORR(v,C[2][0]) CC.push_back(v);
	FORR(v,C[1][1]) CC.push_back(v);
	FORR(v,C[1][0]) CC.push_back(v);
	FORR(v,CC) {
		if(M>=v/2) {
			M-=v/2;
			ret+=v/2*2;
			v%=2;
		}
		else {
			ret+=M*2;
			M=0;
		}
	}
	vector<int> DD;
	FORR(v,C[0][1]) DD.push_back(v);
	FORR(v,C[0][0]) DD.push_back(v);
	sort(ALL(DD));
	reverse(ALL(DD));
	FORR(v,DD) {
		if(M>=(v+1)/2) {
			M-=(v+1)/2;
			ret+=(v+1)/2*2-1;
			v=(v+1)%2;
		}
		else if(M) {
			ret+=M*2-1;
			M=0;
		}
	}
	FORR(v,CC) if(M&&v) ret++, M--;
	FORR(v,DD) if(M&&v) ret++, M--;
	cout<<ret<<endl;
}

まとめ

こういう貪欲、一旦変にはまると解消が難しそう。