kmjp's blog

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

Codeforces #829 : Div1 E. N Machines

Eの割には難しくないかも。
https://codeforces.com/contest/1753/problem/E

問題

N個の機械が並んでいる。
それぞれは入力に対し、

  • 入力値にある値を足す
  • 入力値にある値を掛ける

この一連の機械をつないだうえ、最初の機械に1を入力したときの最後の出力を考える。
ここで、コストPを払うと前者のタイプの機械を1つ任意の場所に移動出来、コストMを払うと後者のタイプの機械を1つ任意の場所に移動出来る。
コストB以下で機械を移動させたとき、出力を最大化せよ。
なお、何も機械を移動させない場合、出力は2*10^9以下である。

解法

足し算は前、掛け算は後ろに持っていくのが良い。
また初期状態の出力の条件より、2以上の掛け算は高々30回である。

同じ値の掛け算をする機械を移動させる場合、後ろのものを優先的に移動させた方が良い。
このことから、掛け算の機械を移動回数を定めた場合に前に持ってくるもののパターンはさほど多くない。
よってそれらを全探索しよう。
掛け算の機械を移動パターンが定まれば、足し算の機械を動かすことの差分は容易に計算できるので、増分が多いものから優先的に動かせばよい。

int N,V,B,P,M;
map<int,int> G;
map<int,int> D,E;
ll ret;

int seg;
vector<ll> As[55],Ss[55];
vector<ll> Ms,M2;

void hoge() {
	E=D;
	M2=Ms;
	ll sum=1,tsum=1;
	int L=B;
	

	FORR(m,M2) {
		sum*=m;
		if(E[m]) {
			tsum*=m;
			E[m]--;
			m=1;
			L-=M;
		}
	}
	if(L<0) return;
	
	int i,j;
	ll tot=sum;
	M2.push_back(tsum);
	for(i=M2.size()-2;i>=0;i--) M2[i]*=M2[i+1];
	ll b=0;
	L=min(V,L/P);
	
	for(i=60;i>=0;i--) {
		int num=0;
		ll tmp=b+(1LL<<i);
		FOR(j,33) if(M2[j]<sum) {
			num+=As[j].end()-lower_bound(ALL(As[j]),(tmp+(sum-M2[j])-1)/(sum-M2[j]));
			if(num>=L) break;
		}
		if(num>=L) b+=1LL<<i;
	}
	tot+=L*b;
	FOR(j,33) {
		tot+=Ss[j].back()*M2[j];
		if(M2[j]<sum) {
			int x=lower_bound(ALL(As[j]),(b+(sum-M2[j])-1)/(sum-M2[j]))-As[j].begin();
			tot+=(Ss[j].back()-Ss[j][x])*(sum-M2[j])-1LL*b*(As[j].size()-x);
		}
	}
	ret=max(ret,tot);
}

void dfs(int cur) {
	auto it=G.lower_bound(cur);
	if(it==G.end()) {
		hoge();
		return;
	}
	int i;
	FOR(i,it->second+1) {
		D[it->first]=i;
		dfs(it->first+1);
	}
}

void solve() {
	int i,j,k,l,r,x,y; string c;
	
	cin>>N>>B>>P>>M;
	FOR(i,33) As[i].reserve(1<<15);
	FOR(i,N) {
		cin>>c>>x;
		
		if(c[0]=='*'&&x==1) {
			i--,N--;
			continue;
		}
		if(c[0]=='*') {
			G[x]++;
			Ms.push_back(x);
		}
		else {
			V++;
			As[Ms.size()].push_back(x);
		}
	}
	
	FOR(i,33) {
		sort(ALL(As[i]));
		Ss[i]={0};
		FORR(a,As[i]) Ss[i].push_back(Ss[i].back()+a);
	}
	dfs(0);
	
	cout<<ret<<endl;
}

まとめ

微妙に探索が面倒くさいな。