kmjp's blog

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

yukicoder : No.974 最後の日までに

先にTLでヒントを見てしまったからなぁ。
https://yukicoder.me/problems/no/974

問題

W週間の各週で、以下の行動をとる。

  • 翌週のデートの予定を取り付ける。
  • 先週に予定を取り付けていたら所持金をC[i]消費し、好感度をB[i]得る。
  • バイトをして所持金をA[i]増やす。

途中所持金が負になってもよいが、最終的には非負でなければならない。
得られる好感度の総和はいくつか。

解法

デートの取り付けとデートは2週連続で行うのがよく、それ以外の日はバイトするのが良い。
結局、W週間中どこをデートに回すかという問題になる。

ここで、Wの最大値が52なので半分全列挙しよう。
前半26日の行動パターンを総当たりして、得られるお金と満足度を求めておく。
その後、その結果を残金が昇順、満足度が降順になるようにしておこう。

次に後半26日の行動パターンを総当たりし、前半の組み合わせのうち所持金の合計が非負になる最適値を二分探索すればよい。
前半の最終日と後半の初日がデートになるパターンに注意。

int W;
ll A[55],B[55],C[55];

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>W;
	FOR(i,W) cin>>A[i]>>B[i]>>C[i];
	
	vector<pair<ll,ll>> cand[2];
	int mask;
	FOR(mask,1<<25) if((mask&(mask>>1))==0) {
		
		if(mask%2==0) {
			ll m=-C[26],s=B[26];
			for(i=27;i<52;i++) {
				if(mask&(1<<(i-26))) {
					i++;
					m-=C[i];
					s+=B[i];
				}
				else {
					m+=A[i];
				}
			}
			cand[1].push_back({m,s});
		}
		ll m=0,s=0;
		for(i=26;i<52;i++) {
			if(mask&(1<<(i-26))) {
				i++;
				m-=C[i];
				s+=B[i];
			}
			else {
				m+=A[i];
			}
		}
		cand[0].push_back({m,s});
	}
	FOR(i,2) {
		sort(ALL(cand[i]));
		vector<pair<ll,ll>> CC;
		FORR(c,cand[i]) {
			while(CC.size() && CC.back().second<=c.second) CC.pop_back();
			CC.push_back(c);
		}
		cand[i]=CC;
		//cout<<i<<endl;
		//FORR(c,CC) cout<<c.first<<" "<<c.second<<endl;
	}
	ll ret=0;
	FOR(mask,1<<26) if((mask&(mask>>1))==0) {
		ll m=0,s=0;
		FOR(i,26) {
			if(mask&(1<<i)) {
				if(i==25) break;
				i++;
				m-=C[i];
				s+=B[i];
			}
			else {
				m+=A[i];
			}
		}
		i=26-i;
		x=lower_bound(ALL(cand[i]),make_pair(-m,0LL))-cand[i].begin();
		if(x<cand[i].size()) ret=max(ret,s+cand[i][x].second);
	}
	cout<<ret<<endl;
}

まとめ

まぁヒントなくてもそのうちたどり着いた…かなぁ。