kmjp's blog

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

TopCoder SRM 608 Div1 Easy MysticAndCandies

SRM608は不参加。復習したらEasyででこずったし、Mediumで1ミスしたし結果だけ見れば出なくてよかった回。
問題はDiv2 Mediumを難しくしたもので、300ptとなっている。
http://community.topcoder.com/stat?c=problem_statement&pm=12997

問題

N個の箱があり、それぞれに飴がlow[i]~high[i]の間のいずれかの個数入っている。
また、合計C個の飴が入っていることがわかっている。

N個の箱のうち幾つかの箱を選んだ時、合計で必ずX個以上の飴が入っているようにしたい。
最小何個の箱を選べば、必ずX個以上となるか。

解法

幾つかの箱を選んだとき、最悪ケースで手に入る飴の数は以下のように考えることができる。
各箱low[i]個の飴は必ず入っており、合計でC-sum(low)の分だけどこに入っているかわからない飴がある。
「どこに入っているかわからない飴」は最悪ケースで選ばなかった箱に入っており、その数はmax(C-sum(low), sum[i∈選択しない箱](high[i]-low[i]))である。
逆に手に入る飴の数は、sum[i∈選択した箱](low[i]) + *1 - max(C-sum(low), sum[i∈選択しない箱](high[i]-low[i])) となる。

よって、飴の数を多くするには、以下の2つのアプローチがある。

  • lowが多いものを優先的に選択し、必ず手に入る量を増やす。
  • highが多いものを優先的に選択し、highが小さいものが選択外となるようにすることで、選択しない箱に流れる飴を減らす。

それぞれlow/highで降順に並べ、先頭a個を選択して条件を満たす最小値を探せばよい。

class MysticAndCandies {
	public:
	int minBoxes(int C, int X, vector <int> low, vector <int> high) {
		vector<pair<int,int> > P;
		int i,j;
		int LT=0;
		int N=low.size(),mi=low.size();
		FOR(i,N) C-=low[i];
		
		FOR(i,N) P.push_back(make_pair(high[i],low[i]));
		
		sort(P.begin(),P.end());
		reverse(P.begin(),P.end());
		for(i=1;i<=N;i++) {
			int lt=0,dt=0;
			FOR(j,i) lt+=P[j].second;
			for(j=i;j<N;j++) dt+=P[j].first-P[j].second;
			dt=min(dt,C);
			if(lt+(C-dt)>=X) mi=min(mi,i);
		}
		
		FOR(i,N) P[i]=make_pair(low[i],high[i]);
		sort(P.begin(),P.end());
		reverse(P.begin(),P.end());
		for(i=1;i<=N;i++) {
			int lt=0,dt=0;
			FOR(j,i) lt+=P[j].first;
			for(j=i;j<N;j++) dt+=P[j].second-P[j].first;
			dt=min(dt,C);
			if(lt+(C-dt)>=X) mi=min(mi,i);
		}
		return mi;
		
		
	}
};

まとめ

low順で選ぶかhigh順で選ぶか迷ったけど、まさかのどっちもやる案でした。

*1:C-sum(low